From 51903f2d3775079b95a28b860bd1299ac8a2a6f2 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 18 Apr 2026 20:36:46 -0600 Subject: [PATCH 01/42] fix closed conversation messages --- web/app/ai-chat/page.js | 2 ++ web/app/chat/page.js | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/web/app/ai-chat/page.js b/web/app/ai-chat/page.js index 13ed9ae4..64f3ae80 100644 --- a/web/app/ai-chat/page.js +++ b/web/app/ai-chat/page.js @@ -440,6 +440,8 @@ function AiChatPage() { setMessages([]); setError(null); setBotTyping(false); + await Promise.all([fetchConversation(convId), fetchMessages(convId)]); + connectStomp(convId); router.replace(`/ai-chat?id=${convId}`, { scroll: false }); } diff --git a/web/app/chat/page.js b/web/app/chat/page.js index 17d7c42c..a52eed4d 100644 --- a/web/app/chat/page.js +++ b/web/app/chat/page.js @@ -422,6 +422,10 @@ function ChatPage() { if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; } setMessages([]); setError(null); + setSwitchingConv(true); + await Promise.all([fetchConversation(convId), fetchMessages(convId)]); + setSwitchingConv(false); + connectStomp(convId); router.replace(`/chat?id=${convId}`, { scroll: false }); } -- 2.49.1 From a85e295c30573e930ccd13e7e3dbcc2d0091d1a1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 17:12:29 -0600 Subject: [PATCH 02/42] force logout on 401 --- .../activities/HomeActivity.java | 28 ++++++++++++++++++- .../api/auth/AuthInterceptor.java | 17 +++++++---- .../petstoremobile/api/auth/TokenManager.java | 12 ++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java index 01b4ca24..0b2d8a1d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java @@ -1,7 +1,10 @@ package com.example.petstoremobile.activities; import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; @@ -20,6 +23,7 @@ import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.ui.NavigationUI; import com.example.petstoremobile.R; +import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.databinding.ActivityHomeBinding; import com.example.petstoremobile.services.ChatNotificationService; @@ -30,6 +34,16 @@ public class HomeActivity extends AppCompatActivity { private ActivityHomeBinding binding; private NavController navController; + private final BroadcastReceiver forceLogoutReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Intent loginIntent = new Intent(HomeActivity.this, MainActivity.class); + loginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(loginIntent); + finish(); + } + }; + // Launcher to ask for notification permission private final ActivityResultLauncher requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { @@ -67,10 +81,22 @@ public class HomeActivity extends AppCompatActivity { handleIntent(getIntent()); } - // Start the notification service and request for notification permission + IntentFilter filter = new IntentFilter(TokenManager.ACTION_FORCE_LOGOUT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(forceLogoutReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + registerReceiver(forceLogoutReceiver, filter); + } + startNotificationService(); requestNotificationPermission(); } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(forceLogoutReceiver); + } /** * Handles new intents received while the activity is already running (like notifications). diff --git a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthInterceptor.java b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthInterceptor.java index 02bbe3c0..0feac644 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthInterceptor.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthInterceptor.java @@ -23,19 +23,26 @@ public class AuthInterceptor implements Interceptor { String token = tokenManager.getToken(); String url = chain.request().url().toString(); - if (url.contains("auth/login") || url.contains("auth/register")) { + boolean isAuthEndpoint = url.contains("auth/login") || url.contains("auth/register"); + + if (isAuthEndpoint) { return chain.proceed(chain.request()); } - //If we have a token then add it to the request + Response response; if (token != null) { Request request = chain.request().newBuilder() .addHeader("Authorization", "Bearer " + token) .build(); - return chain.proceed(request); + response = chain.proceed(request); + } else { + response = chain.proceed(chain.request()); } - //If no token then just pass the request - return chain.proceed(chain.request()); + if (response.code() == 401) { + tokenManager.forceLogout(); + } + + return response; } } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java b/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java index dc6096de..8d53f8e7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java @@ -1,6 +1,7 @@ package com.example.petstoremobile.api.auth; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import javax.inject.Inject; @@ -10,6 +11,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext; @Singleton public class TokenManager { + public static final String ACTION_FORCE_LOGOUT = "com.example.petstoremobile.ACTION_FORCE_LOGOUT"; + private static final String TOKEN_KEY = "token"; private static final String USERNAME_KEY = "username"; private static final String ROLE_KEY = "role"; @@ -17,13 +20,22 @@ public class TokenManager { private static final String USER_ID_KEY = "user_id"; private static final String PRIMARY_STORE_ID_KEY = "primary_store_id"; + private final Context context; private SharedPreferences prefs; @Inject public TokenManager(@ApplicationContext Context context) { + this.context = context; prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); } + public void forceLogout() { + clearLoginData(); + Intent intent = new Intent(ACTION_FORCE_LOGOUT); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent); + } + //save login data after login public void saveLoginData(String token, String username, String role) { prefs.edit() -- 2.49.1 From f8c985efb5d2469af4097501253b3be486705800 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 17:33:48 -0600 Subject: [PATCH 03/42] fix read state tracking --- .../api/ChatRealtimeClient.java | 25 +++++++++++++++++-- .../controllers/ChatController.java | 7 +++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java index 93f90a73..a505c514 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java @@ -14,8 +14,10 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; @@ -43,6 +45,7 @@ public class ChatRealtimeClient implements WebSocket.Listener { private volatile String currentStatus = "Chat disconnected"; private final Map globalConversations = new HashMap<>(); + private final Set readConversationIds = new HashSet<>(); private final List> notificationListeners = new ArrayList<>(); private boolean lastNotificationState = false; @@ -79,10 +82,24 @@ public class ChatRealtimeClient implements WebSocket.Listener { if (conv != null) { conv.setLastSenderId(senderId); } + readConversationIds.add(conversationId); } updateNotificationState(); } + public void markConversationRead(Long conversationId) { + synchronized (lock) { + readConversationIds.add(conversationId); + } + updateNotificationState(); + } + + public boolean isConversationRead(Long conversationId) { + synchronized (lock) { + return readConversationIds.contains(conversationId); + } + } + public boolean hasActionableChats() { synchronized (lock) { UserSession session = UserSession.getInstance(); @@ -98,7 +115,8 @@ public class ChatRealtimeClient implements WebSocket.Listener { // Needs reply (assigned to me and last sender was someone else - customer) if (currentUserId != null && currentUserId.equals(conv.getStaffId())) { - if (conv.getLastSenderId() != null && !conv.getLastSenderId().equals(currentUserId)) { + if (conv.getLastSenderId() != null && !conv.getLastSenderId().equals(currentUserId) + && !readConversationIds.contains(conv.getId())) { return true; } } @@ -302,6 +320,7 @@ public class ChatRealtimeClient implements WebSocket.Listener { conversationsSubscriptionId = null; conversationMessagesSubscriptionId = null; globalConversations.clear(); + readConversationIds.clear(); updateNotificationState(); } @@ -352,13 +371,15 @@ public class ChatRealtimeClient implements WebSocket.Listener { .notifyNewMessage(message.getSenderDisplayName(), message.getContent()); } - // Also update globalConversation last sender if this is the active conversation synchronized (lock) { ConversationResponse conv = globalConversations.get(message.getConversationId()); if (conv != null) { conv.setLastMessage(message.getContent()); conv.setLastSenderId(message.getSenderId()); } + if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) { + readConversationIds.remove(message.getConversationId()); + } } updateNotificationState(); } else { 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 4417bd23..3ba9e995 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java @@ -282,6 +282,8 @@ public class ChatController { realtimeClient.subscribeToConversation(newValue.getId()); } updateChatState(newValue); + realtimeClient.markConversationRead(newValue.getId()); + refreshSections(); } private void updateChatState(ConversationResponse conv) { @@ -320,7 +322,8 @@ public class ChatController { Long currentUserId = session.getUserId(); boolean needsPickup = item.getHumanRequestedAt() != null && item.getStaffId() == null; boolean needsReply = currentUserId != null && currentUserId.equals(item.getStaffId()) - && item.getLastSenderId() != null && !item.getLastSenderId().equals(currentUserId); + && item.getLastSenderId() != null && !item.getLastSenderId().equals(currentUserId) + && !ChatRealtimeClient.getInstance().isConversationRead(item.getId()); if (needsPickup || needsReply) { title.setStyle("-fx-font-weight: bold; -fx-text-fill: #FF6B6B;"); @@ -460,6 +463,8 @@ public class ChatController { vbMessages.getChildren().add(createMessageBubble(message)); if (message.getId() != null) renderedMessageIds.add(message.getId()); scrollMessagesToBottom(); + realtimeClient.markConversationRead(message.getConversationId()); + refreshSections(); } } catch (Exception e) { ActivityLogger.getInstance().logException( -- 2.49.1 From 5fd7a333cba7d8aa6777330b459b03a1818867d9 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 17:42:30 -0600 Subject: [PATCH 04/42] flatten flyway migrations --- .../backend/config/DataInitializer.java | 15 ++ backend/src/main/resources/application.yml | 1 + .../db/migration/V1__target_baseline.sql | 7 +- .../resources/db/migration/V2__seed_data.sql | 243 +++++++++++++++++- .../V3__nullable_appointment_petid.sql | 3 - .../V4__drop_purchase_order_status.sql | 1 - .../db/migration/V5__seed_data_2026.sql | 87 ------- .../db/migration/V6__unique_constraints.sql | 1 - .../db/migration/V7__fix_service_species.sql | 14 - .../db/migration/V8__seed_activity_logs.sql | 90 ------- .../db/migration/V9__seed_recent_sales.sql | 51 ---- 11 files changed, 261 insertions(+), 252 deletions(-) delete mode 100644 backend/src/main/resources/db/migration/V3__nullable_appointment_petid.sql delete mode 100644 backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql delete mode 100644 backend/src/main/resources/db/migration/V5__seed_data_2026.sql delete mode 100644 backend/src/main/resources/db/migration/V6__unique_constraints.sql delete mode 100644 backend/src/main/resources/db/migration/V7__fix_service_species.sql delete mode 100644 backend/src/main/resources/db/migration/V8__seed_activity_logs.sql delete mode 100644 backend/src/main/resources/db/migration/V9__seed_recent_sales.sql diff --git a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java index 9809ddc6..89cbb18e 100644 --- a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -34,6 +34,7 @@ public class DataInitializer implements CommandLineRunner { admin.setPhone("000-000-1000"); admin.setRole(User.Role.ADMIN); admin.setActive(true); + admin.setAvatarUrl("/uploads/avatars/001.webp"); userRepository.save(admin); System.out.println("Admin user created successfully"); } else { @@ -67,6 +68,10 @@ public class DataInitializer implements CommandLineRunner { admin.setRole(User.Role.ADMIN); updated = true; } + if (admin.getAvatarUrl() == null || admin.getAvatarUrl().isEmpty()) { + admin.setAvatarUrl("/uploads/avatars/001.webp"); + updated = true; + } if (updated) { userRepository.save(admin); System.out.println("Admin user normalized"); @@ -86,6 +91,7 @@ public class DataInitializer implements CommandLineRunner { staff.setPhone("000-000-1001"); staff.setRole(User.Role.STAFF); staff.setActive(true); + staff.setAvatarUrl("/uploads/avatars/003.webp"); userRepository.save(staff); System.out.println("Staff user created successfully"); } else { @@ -119,6 +125,10 @@ public class DataInitializer implements CommandLineRunner { staff.setRole(User.Role.STAFF); updated = true; } + if (staff.getAvatarUrl() == null || staff.getAvatarUrl().isEmpty()) { + staff.setAvatarUrl("/uploads/avatars/003.webp"); + updated = true; + } if (updated) { userRepository.save(staff); System.out.println("Staff user normalized"); @@ -138,6 +148,7 @@ public class DataInitializer implements CommandLineRunner { customer.setPhone("000-000-1002"); customer.setRole(User.Role.CUSTOMER); customer.setActive(true); + customer.setAvatarUrl("/uploads/avatars/015.webp"); userRepository.save(customer); System.out.println("Customer user created successfully"); } else { @@ -171,6 +182,10 @@ public class DataInitializer implements CommandLineRunner { customer.setRole(User.Role.CUSTOMER); updated = true; } + if (customer.getAvatarUrl() == null || customer.getAvatarUrl().isEmpty()) { + customer.setAvatarUrl("/uploads/avatars/015.webp"); + updated = true; + } if (updated) { userRepository.save(customer); System.out.println("Customer user normalized"); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 20773f86..21d7f6d4 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -40,6 +40,7 @@ spring: flyway: enabled: ${FLYWAY_ENABLED:false} + validate-on-migrate: false server: port: ${SERVER_PORT:8080} diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql index bc064913..6f03cf58 100644 --- a/backend/src/main/resources/db/migration/V1__target_baseline.sql +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS users ( firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, fullName VARCHAR(100) NULL, - phone VARCHAR(20) NULL, + phone VARCHAR(20) NULL UNIQUE, avatarUrl VARCHAR(255) NULL, role VARCHAR(20) NOT NULL, staffRole VARCHAR(50) NULL, @@ -108,7 +108,6 @@ CREATE TABLE IF NOT EXISTS purchaseOrder ( supId BIGINT NOT NULL, storeId BIGINT NOT NULL, orderDate DATE NOT NULL, - status VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_purchase_order_supplier FOREIGN KEY (supId) REFERENCES supplier(supId), @@ -150,7 +149,7 @@ CREATE TABLE IF NOT EXISTS pet ( CREATE TABLE IF NOT EXISTS appointment ( appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, serviceId BIGINT NOT NULL, - petId BIGINT NOT NULL, + petId BIGINT NULL, customerId BIGINT NOT NULL, storeId BIGINT NOT NULL, employeeId BIGINT NOT NULL, @@ -160,7 +159,7 @@ CREATE TABLE IF NOT EXISTS appointment ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_appointment_service FOREIGN KEY (serviceId) REFERENCES service(serviceId), - CONSTRAINT fk_appointment_pet FOREIGN KEY (petId) REFERENCES pet(petId), + CONSTRAINT fk_appointment_pet FOREIGN KEY (petId) REFERENCES pet(petId) ON DELETE SET NULL, CONSTRAINT fk_appointment_customer FOREIGN KEY (customerId) REFERENCES users(id), CONSTRAINT fk_appointment_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), CONSTRAINT fk_appointment_employee FOREIGN KEY (employeeId) REFERENCES users(id) diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 88d3835b..befa56fa 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -204,11 +204,14 @@ INSERT INTO service_species (serviceId, species) VALUES (2, 'Rabbit'), (2, 'Guinea Pig'), (2, 'Hamster'), -(2, 'Bird'), +(2, 'Reptile'), +(2, 'Other'), (3, 'Dog'), (3, 'Cat'), (3, 'Rabbit'), (3, 'Guinea Pig'), +(3, 'Reptile'), +(3, 'Other'), (4, 'Dog'), (4, 'Cat'), (4, 'Rabbit'), @@ -216,11 +219,18 @@ INSERT INTO service_species (serviceId, species) VALUES (4, 'Fish'), (4, 'Hamster'), (4, 'Guinea Pig'), +(4, 'Reptile'), +(4, 'Other'), (5, 'Dog'), (5, 'Cat'), (5, 'Rabbit'), (5, 'Guinea Pig'), (5, 'Hamster'), +(5, 'Reptile'), +(5, 'Other'), +(1, 'Guinea Pig'), +(1, 'Hamster'), +(1, 'Other'), (6, 'Bird'), (7, 'Bird'), (8, 'Fish'); @@ -2247,3 +2257,234 @@ WHERE imageUrl LIKE 'https://images.petshop.local/products/%'; UPDATE storeLocation SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/stores/', '/stores/') WHERE imageUrl LIKE 'https://images.petshop.local/stores/%'; + +INSERT IGNORE INTO appointment (appointmentId, serviceId, petId, customerId, storeId, employeeId, appointmentDate, appointmentTime, appointmentStatus) VALUES +(91, 1, NULL, 16, 1, 3, '2026-03-08', '09:00:00', 'COMPLETED'), +(92, 2, NULL, 17, 2, 8, '2026-03-10', '10:30:00', 'COMPLETED'), +(93, 3, NULL, 18, 3, 13, '2026-03-12', '13:00:00', 'MISSED'), +(94, 4, NULL, 19, 1, 6, '2026-03-14', '14:30:00', 'COMPLETED'), +(95, 5, NULL, 20, 2, 7, '2026-03-16', '09:00:00', 'COMPLETED'), +(96, 6, NULL, 21, 3, 12, '2026-03-18', '10:30:00', 'COMPLETED'), +(97, 7, NULL, 22, 1, 5, '2026-03-20', '13:00:00', 'CANCELLED'), +(98, 8, NULL, 23, 2, 10, '2026-03-22', '14:30:00', 'COMPLETED'), +(99, 1, NULL, 24, 3, 11, '2026-03-24', '09:00:00', 'COMPLETED'), +(100, 2, NULL, 25, 1, 4, '2026-03-26', '10:30:00', 'MISSED'), +(101, 3, NULL, 26, 2, 9, '2026-03-28', '13:00:00', 'COMPLETED'), +(102, 4, NULL, 27, 3, 14, '2026-03-30', '14:30:00', 'COMPLETED'), +(103, 5, NULL, 28, 1, 3, '2026-04-01', '09:00:00', 'COMPLETED'), +(104, 6, NULL, 29, 2, 8, '2026-04-03', '10:30:00', 'COMPLETED'), +(105, 7, NULL, 30, 3, 13, '2026-04-05', '13:00:00', 'MISSED'), +(106, 8, NULL, 31, 1, 6, '2026-04-07', '14:30:00', 'COMPLETED'), +(107, 1, NULL, 32, 2, 7, '2026-04-09', '09:00:00', 'COMPLETED'), +(108, 2, NULL, 33, 3, 12, '2026-04-11', '10:30:00', 'CANCELLED'), +(109, 3, NULL, 34, 1, 5, '2026-04-13', '13:00:00', 'COMPLETED'), +(110, 4, NULL, 35, 2, 10, '2026-04-15', '10:00:00', 'SCHEDULED'), +(111, 5, NULL, 36, 3, 11, '2026-04-16', '14:00:00', 'SCHEDULED'); + +INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES +(111, '2026-03-08 09:15:00', 87.50, 'Cash', 3, 1, 3, 0, NULL, 'IN_STORE', NULL, NULL, 87.50, 0.00, 0.00, 8), +(112, '2026-03-09 10:22:00', 145.20, 'Card', 8, 2, 4, 0, NULL, 'IN_STORE', NULL, NULL, 145.20, 0.00, 0.00, 14), +(113, '2026-03-10 11:33:00', 63.75, 'Cash', 13, 3, 5, 0, NULL, 'IN_STORE', NULL, NULL, 63.75, 0.00, 0.00, 6), +(114, '2026-03-11 12:44:00', 210.00, 'Card', 6, 1, 6, 0, NULL, 'ONLINE', NULL, NULL, 210.00, 0.00, 0.00, 21), +(115, '2026-03-12 13:55:00', 38.90, 'Cash', 7, 2, 7, 0, NULL, 'IN_STORE', NULL, NULL, 38.90, 0.00, 0.00, 3), +(116, '2026-03-14 09:10:00', 325.40, 'Card', 12, 3, 8, 0, NULL, 'ONLINE', NULL, NULL, 325.40, 0.00, 0.00, 32), +(117, '2026-03-16 10:25:00', 72.15, 'Cash', 5, 1, 9, 0, NULL, 'IN_STORE', NULL, NULL, 72.15, 0.00, 0.00, 7), +(118, '2026-03-18 11:40:00', 190.80, 'Card', 10, 2, 10, 0, NULL, 'ONLINE', NULL, NULL, 190.80, 0.00, 0.00, 19), +(119, '2026-03-20 12:55:00', 55.30, 'Cash', 11, 3, 11, 0, NULL, 'IN_STORE', NULL, NULL, 55.30, 0.00, 0.00, 5), +(120, '2026-03-22 14:10:00', 412.60, 'Card', 4, 1, 12, 0, NULL, 'ONLINE', NULL, NULL, 412.60, 0.00, 0.00, 41), +(121, '2026-03-24 09:30:00', 98.45, 'Cash', 9, 2, 13, 0, NULL, 'IN_STORE', NULL, NULL, 98.45, 0.00, 0.00, 9), +(122, '2026-03-26 10:45:00', 167.70, 'Card', 14, 3, 14, 0, NULL, 'ONLINE', NULL, NULL, 167.70, 0.00, 0.00, 16), +(123, '2026-03-28 12:00:00', 44.20, 'Cash', 3, 1, 15, 0, NULL, 'IN_STORE', NULL, NULL, 44.20, 0.00, 0.00, 4), +(124, '2026-03-30 13:15:00', 289.55, 'Card', 8, 2, 16, 0, NULL, 'ONLINE', NULL, NULL, 289.55, 0.00, 0.00, 28), +(125, '2026-04-01 09:20:00', 76.80, 'Cash', 13, 3, 17, 0, NULL, 'IN_STORE', NULL, NULL, 76.80, 0.00, 0.00, 7), +(126, '2026-04-03 10:35:00', 234.10, 'Card', 6, 1, 18, 0, NULL, 'ONLINE', NULL, NULL, 234.10, 0.00, 0.00, 23), +(127, '2026-04-05 11:50:00', 52.40, 'Cash', 7, 2, 19, 0, NULL, 'IN_STORE', NULL, NULL, 52.40, 0.00, 0.00, 5), +(128, '2026-04-07 13:05:00', 178.90, 'Card', 12, 3, 20, 0, NULL, 'ONLINE', NULL, NULL, 178.90, 0.00, 0.00, 17), +(129, '2026-04-09 09:15:00', 115.60, 'Cash', 5, 1, 21, 0, NULL, 'IN_STORE', NULL, NULL, 115.60, 0.00, 0.00, 11), +(130, '2026-04-11 10:30:00', 367.25, 'Card', 10, 2, 22, 0, NULL, 'ONLINE', NULL, NULL, 367.25, 0.00, 0.00, 36), +(131, '2026-04-14 11:45:00', 89.70, 'Cash', 11, 3, 23, 0, NULL, 'IN_STORE', NULL, NULL, 89.70, 0.00, 0.00, 8), +(132, '2026-04-15 09:00:00', 145.30, 'Card', 4, 1, 24, 0, NULL, 'ONLINE', NULL, NULL, 145.30, 0.00, 0.00, 14), +(133, '2026-04-16 10:00:00', 78.60, 'Cash', 9, 2, 25, 0, NULL, 'IN_STORE', NULL, NULL, 78.60, 0.00, 0.00, 7); + +INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES +(226, 111, 5, 2, 25.50), +(227, 111, 18, 1, 36.50), +(228, 112, 22, 3, 29.80), +(229, 112, 7, 1, 55.80), +(230, 113, 11, 1, 43.75), +(231, 113, 3, 1, 20.00), +(232, 114, 15, 4, 40.00), +(233, 114, 29, 1, 50.00), +(234, 115, 8, 1, 38.90), +(235, 116, 20, 2, 95.00), +(236, 116, 33, 1, 135.40), +(237, 117, 6, 1, 72.15), +(238, 118, 14, 3, 45.00), +(239, 118, 25, 1, 55.80), +(240, 119, 9, 1, 55.30), +(241, 120, 17, 2, 125.00), +(242, 120, 31, 1, 162.60), +(243, 121, 2, 2, 35.50), +(244, 121, 10, 1, 27.45), +(245, 122, 23, 2, 58.50), +(246, 122, 36, 1, 50.70), +(247, 123, 4, 1, 44.20), +(248, 124, 19, 3, 65.00), +(249, 124, 28, 1, 94.55), +(250, 125, 12, 1, 76.80), +(251, 126, 16, 2, 80.00), +(252, 126, 27, 1, 74.10), +(253, 127, 7, 1, 52.40), +(254, 128, 21, 2, 65.00), +(255, 128, 32, 1, 48.90), +(256, 129, 13, 2, 47.80), +(257, 129, 1, 1, 20.00), +(258, 130, 24, 3, 80.00), +(259, 130, 37, 1, 127.25), +(260, 131, 6, 1, 89.70), +(261, 132, 15, 2, 55.00), +(262, 132, 29, 1, 35.30), +(263, 133, 8, 1, 78.60); + +INSERT INTO activityLog (userId, storeId, usernameSnapshot, fullNameSnapshot, roleSnapshot, storeNameSnapshot, activity, logTimestamp) VALUES +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 08:02:11'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-01-15 08:15:44'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-01-15 08:31:07'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:00:00'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:04:22'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 10:12:33'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-01-15 10:18:05'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-01-15 10:25:50'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #12 | PUT /api/v1/appointments/12 → 200', '2026-01-16 11:05:30'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-17 07:58:44'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated pet #8 | PUT /api/v1/pets/8 → 200', '2026-01-17 08:10:19'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 09:01:55'), +(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 14:30:22'), +(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-01-20 14:45:08'), +(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-22 08:55:00'), +(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated pet #14 | PUT /api/v1/pets/14 → 200', '2026-01-22 09:20:17'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-25 08:00:00'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-01-25 08:22:41'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new employee | POST /api/v1/employees → 201', '2026-01-25 09:05:14'), +(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-28 18:30:00'), +(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-01-28 18:35:12'), +(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-01-28 18:40:55'), +(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-03 09:00:00'), +(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-03 10:15:38'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-05 07:50:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Deleted pet #3 | DELETE /api/v1/pets/3 → 200', '2026-02-05 08:05:33'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #22 | PUT /api/v1/products/22 → 200', '2026-02-05 08:30:44'), +(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-07 12:00:00'), +(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-02-07 12:08:17'), +(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Applied a coupon to cart | POST /api/v1/cart/apply-coupon → 200', '2026-02-07 12:12:05'), +(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-07 12:20:30'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-10 09:00:00'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-02-10 09:30:22'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Updated appointment #25 | PUT /api/v1/appointments/25 → 200', '2026-02-10 11:45:09'), +(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-14 15:00:00'), +(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-02-14 15:05:44'), +(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/5/messages → 201', '2026-02-14 15:08:22'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-17 09:00:00'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Uploaded image for pet #31 | POST /api/v1/pets/31/image → 200', '2026-02-17 09:25:11'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-20 08:00:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-02-20 08:20:35'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #18 | PUT /api/v1/users/18 → 200', '2026-02-20 09:10:00'), +(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-22 19:00:00'), +(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-02-22 19:15:40'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-01 09:00:00'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-01 10:30:15'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated appointment #33 | PUT /api/v1/appointments/33 → 200', '2026-03-01 14:05:22'), +(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-05 11:00:00'), +(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-03-05 11:10:30'), +(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Started checkout | POST /api/v1/cart/checkout → 200', '2026-03-05 11:18:44'), +(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-05 11:22:17'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-08 08:00:00'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Deleted multiple pets | POST /api/v1/pets/bulk-delete → 200', '2026-03-08 08:30:00'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-10 09:00:00'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-10 10:45:33'), +(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-12 16:00:00'), +(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-03-12 16:05:22'), +(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-03-12 16:20:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-15 07:55:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-03-15 08:10:45'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #35 | PUT /api/v1/products/35 → 200', '2026-03-15 08:40:12'), +(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-18 09:00:00'), +(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated appointment #45 | PUT /api/v1/appointments/45 → 200', '2026-03-18 09:35:08'), +(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-20 20:00:00'), +(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-03-20 20:05:30'), +(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/9/messages → 201', '2026-03-20 20:08:11'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-24 09:00:00'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-03-24 09:20:44'), +(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-27 09:00:00'), +(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated appointment #52 | PUT /api/v1/appointments/52 → 200', '2026-03-27 10:10:19'), +(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-27 11:30:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-01 08:00:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-04-01 08:15:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #25 | PUT /api/v1/users/25 → 200', '2026-04-01 09:00:22'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-05 10:00:00'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-05 10:15:00'), +(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-05 10:25:44'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-07 09:00:00'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated pet #47 | PUT /api/v1/pets/47 → 200', '2026-04-07 09:40:55'), +(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-09 17:00:00'), +(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-04-09 17:20:33'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-12 08:00:00'), +(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-04-12 08:25:18'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-14 09:00:00'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-14 10:55:30'), +(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #60 | PUT /api/v1/appointments/60 → 200', '2026-04-14 14:20:00'), +(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-15 11:00:00'), +(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-15 11:10:22'), +(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-15 11:30:00'); + +INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES +(134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'IN_STORE', NULL, NULL, 57.67, 0.00, 0.00, 5), +(135, '2026-04-10 11:30:00', 143.55, 'Card', 8, 2, 17, 0, NULL, 'ONLINE', NULL, NULL, 143.55, 0.00, 0.00, 14), +(136, '2026-04-10 14:45:00', 50.09, 'Cash', 12, 3, 18, 0, NULL, 'IN_STORE', NULL, NULL, 50.09, 0.00, 0.00, 5), +(137, '2026-04-11 10:00:00', 114.48, 'Card', 5, 1, 19, 0, NULL, 'ONLINE', NULL, NULL, 114.48, 0.00, 0.00, 11), +(138, '2026-04-11 13:20:00', 93.55, 'Cash', 9, 2, 20, 0, NULL, 'IN_STORE', NULL, NULL, 93.55, 0.00, 0.00, 9), +(139, '2026-04-12 09:45:00', 100.71, 'Card', 13, 3, 21, 0, NULL, 'ONLINE', NULL, NULL, 100.71, 0.00, 0.00, 10), +(140, '2026-04-12 11:00:00', 51.07, 'Cash', 6, 1, 22, 0, NULL, 'IN_STORE', NULL, NULL, 51.07, 0.00, 0.00, 5), +(141, '2026-04-12 15:30:00', 139.66, 'Card', 7, 2, 23, 0, NULL, 'ONLINE', NULL, NULL, 139.66, 0.00, 0.00, 13), +(142, '2026-04-13 09:00:00', 73.98, 'Cash', 14, 3, 24, 0, NULL, 'IN_STORE', NULL, NULL, 73.98, 0.00, 0.00, 7), +(143, '2026-04-13 12:15:00', 134.76, 'Card', 4, 1, 25, 0, NULL, 'ONLINE', NULL, NULL, 134.76, 0.00, 0.00, 13), +(144, '2026-04-14 10:30:00', 80.40, 'Cash', 10, 2, 26, 0, NULL, 'IN_STORE', NULL, NULL, 80.40, 0.00, 0.00, 8), +(145, '2026-04-14 14:00:00', 125.90, 'Card', 11, 3, 27, 0, NULL, 'ONLINE', NULL, NULL, 125.90, 0.00, 0.00, 12), +(146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'IN_STORE', NULL, NULL, 80.62, 0.00, 0.00, 8), +(147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'ONLINE', NULL, NULL, 141.28, 0.00, 0.00, 14), +(148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'IN_STORE', NULL, NULL, 97.85, 0.00, 0.00, 9), +(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8); + +INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES +(264, 134, 1, 2, 25.09), +(265, 134, 11, 1, 7.49), +(266, 135, 7, 2, 57.62), +(267, 135, 25, 1, 28.31), +(268, 136, 3, 1, 35.93), +(269, 136, 14, 1, 14.16), +(270, 137, 15, 3, 16.38), +(271, 137, 26, 2, 32.67), +(272, 138, 8, 1, 63.05), +(273, 138, 22, 2, 15.25), +(274, 139, 20, 2, 27.49), +(275, 139, 29, 1, 45.73), +(276, 140, 4, 1, 41.36), +(277, 140, 12, 1, 9.71), +(278, 141, 34, 2, 59.42), +(279, 141, 17, 1, 20.82), +(280, 142, 6, 1, 52.20), +(281, 142, 21, 2, 10.89), +(282, 143, 37, 1, 97.56), +(283, 143, 16, 2, 18.60), +(284, 144, 9, 1, 68.47), +(285, 144, 13, 1, 11.93), +(286, 145, 19, 3, 25.27), +(287, 145, 30, 1, 50.09), +(288, 146, 2, 2, 30.51), +(289, 146, 23, 1, 19.60), +(290, 147, 35, 1, 72.13), +(291, 147, 18, 3, 23.05), +(292, 148, 10, 1, 73.89), +(293, 148, 24, 1, 23.96), +(294, 149, 31, 2, 21.29), +(295, 149, 5, 1, 46.78); diff --git a/backend/src/main/resources/db/migration/V3__nullable_appointment_petid.sql b/backend/src/main/resources/db/migration/V3__nullable_appointment_petid.sql deleted file mode 100644 index 06639401..00000000 --- a/backend/src/main/resources/db/migration/V3__nullable_appointment_petid.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE appointment MODIFY petId BIGINT NULL; -ALTER TABLE appointment DROP FOREIGN KEY fk_appointment_pet; -ALTER TABLE appointment ADD CONSTRAINT fk_appointment_pet FOREIGN KEY (petId) REFERENCES pet(petId) ON DELETE SET NULL; diff --git a/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql b/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql deleted file mode 100644 index bef741cf..00000000 --- a/backend/src/main/resources/db/migration/V4__drop_purchase_order_status.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE purchaseOrder DROP COLUMN status; diff --git a/backend/src/main/resources/db/migration/V5__seed_data_2026.sql b/backend/src/main/resources/db/migration/V5__seed_data_2026.sql deleted file mode 100644 index 3f8f0a40..00000000 --- a/backend/src/main/resources/db/migration/V5__seed_data_2026.sql +++ /dev/null @@ -1,87 +0,0 @@ -INSERT IGNORE INTO appointment (appointmentId, serviceId, petId, customerId, storeId, employeeId, appointmentDate, appointmentTime, appointmentStatus) VALUES -(91, 1, NULL, 16, 1, 3, '2026-03-08', '09:00:00', 'COMPLETED'), -(92, 2, NULL, 17, 2, 8, '2026-03-10', '10:30:00', 'COMPLETED'), -(93, 3, NULL, 18, 3, 13, '2026-03-12', '13:00:00', 'MISSED'), -(94, 4, NULL, 19, 1, 6, '2026-03-14', '14:30:00', 'COMPLETED'), -(95, 5, NULL, 20, 2, 7, '2026-03-16', '09:00:00', 'COMPLETED'), -(96, 6, NULL, 21, 3, 12, '2026-03-18', '10:30:00', 'COMPLETED'), -(97, 7, NULL, 22, 1, 5, '2026-03-20', '13:00:00', 'CANCELLED'), -(98, 8, NULL, 23, 2, 10, '2026-03-22', '14:30:00', 'COMPLETED'), -(99, 1, NULL, 24, 3, 11, '2026-03-24', '09:00:00', 'COMPLETED'), -(100, 2, NULL, 25, 1, 4, '2026-03-26', '10:30:00', 'MISSED'), -(101, 3, NULL, 26, 2, 9, '2026-03-28', '13:00:00', 'COMPLETED'), -(102, 4, NULL, 27, 3, 14, '2026-03-30', '14:30:00', 'COMPLETED'), -(103, 5, NULL, 28, 1, 3, '2026-04-01', '09:00:00', 'COMPLETED'), -(104, 6, NULL, 29, 2, 8, '2026-04-03', '10:30:00', 'COMPLETED'), -(105, 7, NULL, 30, 3, 13, '2026-04-05', '13:00:00', 'MISSED'), -(106, 8, NULL, 31, 1, 6, '2026-04-07', '14:30:00', 'COMPLETED'), -(107, 1, NULL, 32, 2, 7, '2026-04-09', '09:00:00', 'COMPLETED'), -(108, 2, NULL, 33, 3, 12, '2026-04-11', '10:30:00', 'CANCELLED'), -(109, 3, NULL, 34, 1, 5, '2026-04-13', '13:00:00', 'COMPLETED'), -(110, 4, NULL, 35, 2, 10, '2026-04-15', '10:00:00', 'SCHEDULED'), -(111, 5, NULL, 36, 3, 11, '2026-04-16', '14:00:00', 'SCHEDULED'); - -INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES -(111, '2026-03-08 09:15:00', 87.50, 'Cash', 3, 1, 3, 0, NULL, 'IN_STORE', NULL, NULL, 87.50, 0.00, 0.00, 8), -(112, '2026-03-09 10:22:00', 145.20, 'Card', 8, 2, 4, 0, NULL, 'IN_STORE', NULL, NULL, 145.20, 0.00, 0.00, 14), -(113, '2026-03-10 11:33:00', 63.75, 'Cash', 13, 3, 5, 0, NULL, 'IN_STORE', NULL, NULL, 63.75, 0.00, 0.00, 6), -(114, '2026-03-11 12:44:00', 210.00, 'Card', 6, 1, 6, 0, NULL, 'ONLINE', NULL, NULL, 210.00, 0.00, 0.00, 21), -(115, '2026-03-12 13:55:00', 38.90, 'Cash', 7, 2, 7, 0, NULL, 'IN_STORE', NULL, NULL, 38.90, 0.00, 0.00, 3), -(116, '2026-03-14 09:10:00', 325.40, 'Card', 12, 3, 8, 0, NULL, 'ONLINE', NULL, NULL, 325.40, 0.00, 0.00, 32), -(117, '2026-03-16 10:25:00', 72.15, 'Cash', 5, 1, 9, 0, NULL, 'IN_STORE', NULL, NULL, 72.15, 0.00, 0.00, 7), -(118, '2026-03-18 11:40:00', 190.80, 'Card', 10, 2, 10, 0, NULL, 'ONLINE', NULL, NULL, 190.80, 0.00, 0.00, 19), -(119, '2026-03-20 12:55:00', 55.30, 'Cash', 11, 3, 11, 0, NULL, 'IN_STORE', NULL, NULL, 55.30, 0.00, 0.00, 5), -(120, '2026-03-22 14:10:00', 412.60, 'Card', 4, 1, 12, 0, NULL, 'ONLINE', NULL, NULL, 412.60, 0.00, 0.00, 41), -(121, '2026-03-24 09:30:00', 98.45, 'Cash', 9, 2, 13, 0, NULL, 'IN_STORE', NULL, NULL, 98.45, 0.00, 0.00, 9), -(122, '2026-03-26 10:45:00', 167.70, 'Card', 14, 3, 14, 0, NULL, 'ONLINE', NULL, NULL, 167.70, 0.00, 0.00, 16), -(123, '2026-03-28 12:00:00', 44.20, 'Cash', 3, 1, 15, 0, NULL, 'IN_STORE', NULL, NULL, 44.20, 0.00, 0.00, 4), -(124, '2026-03-30 13:15:00', 289.55, 'Card', 8, 2, 16, 0, NULL, 'ONLINE', NULL, NULL, 289.55, 0.00, 0.00, 28), -(125, '2026-04-01 09:20:00', 76.80, 'Cash', 13, 3, 17, 0, NULL, 'IN_STORE', NULL, NULL, 76.80, 0.00, 0.00, 7), -(126, '2026-04-03 10:35:00', 234.10, 'Card', 6, 1, 18, 0, NULL, 'ONLINE', NULL, NULL, 234.10, 0.00, 0.00, 23), -(127, '2026-04-05 11:50:00', 52.40, 'Cash', 7, 2, 19, 0, NULL, 'IN_STORE', NULL, NULL, 52.40, 0.00, 0.00, 5), -(128, '2026-04-07 13:05:00', 178.90, 'Card', 12, 3, 20, 0, NULL, 'ONLINE', NULL, NULL, 178.90, 0.00, 0.00, 17), -(129, '2026-04-09 09:15:00', 115.60, 'Cash', 5, 1, 21, 0, NULL, 'IN_STORE', NULL, NULL, 115.60, 0.00, 0.00, 11), -(130, '2026-04-11 10:30:00', 367.25, 'Card', 10, 2, 22, 0, NULL, 'ONLINE', NULL, NULL, 367.25, 0.00, 0.00, 36), -(131, '2026-04-14 11:45:00', 89.70, 'Cash', 11, 3, 23, 0, NULL, 'IN_STORE', NULL, NULL, 89.70, 0.00, 0.00, 8), -(132, '2026-04-15 09:00:00', 145.30, 'Card', 4, 1, 24, 0, NULL, 'ONLINE', NULL, NULL, 145.30, 0.00, 0.00, 14), -(133, '2026-04-16 10:00:00', 78.60, 'Cash', 9, 2, 25, 0, NULL, 'IN_STORE', NULL, NULL, 78.60, 0.00, 0.00, 7); - -INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES -(226, 111, 5, 2, 25.50), -(227, 111, 18, 1, 36.50), -(228, 112, 22, 3, 29.80), -(229, 112, 7, 1, 55.80), -(230, 113, 11, 1, 43.75), -(231, 113, 3, 1, 20.00), -(232, 114, 15, 4, 40.00), -(233, 114, 29, 1, 50.00), -(234, 115, 8, 1, 38.90), -(235, 116, 20, 2, 95.00), -(236, 116, 33, 1, 135.40), -(237, 117, 6, 1, 72.15), -(238, 118, 14, 3, 45.00), -(239, 118, 25, 1, 55.80), -(240, 119, 9, 1, 55.30), -(241, 120, 17, 2, 125.00), -(242, 120, 31, 1, 162.60), -(243, 121, 2, 2, 35.50), -(244, 121, 10, 1, 27.45), -(245, 122, 23, 2, 58.50), -(246, 122, 36, 1, 50.70), -(247, 123, 4, 1, 44.20), -(248, 124, 19, 3, 65.00), -(249, 124, 28, 1, 94.55), -(250, 125, 12, 1, 76.80), -(251, 126, 16, 2, 80.00), -(252, 126, 27, 1, 74.10), -(253, 127, 7, 1, 52.40), -(254, 128, 21, 2, 65.00), -(255, 128, 32, 1, 48.90), -(256, 129, 13, 2, 47.80), -(257, 129, 1, 1, 20.00), -(258, 130, 24, 3, 80.00), -(259, 130, 37, 1, 127.25), -(260, 131, 6, 1, 89.70), -(261, 132, 15, 2, 55.00), -(262, 132, 29, 1, 35.30), -(263, 133, 8, 1, 78.60); diff --git a/backend/src/main/resources/db/migration/V6__unique_constraints.sql b/backend/src/main/resources/db/migration/V6__unique_constraints.sql deleted file mode 100644 index 5e26b311..00000000 --- a/backend/src/main/resources/db/migration/V6__unique_constraints.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users ADD CONSTRAINT uq_users_phone UNIQUE (phone); diff --git a/backend/src/main/resources/db/migration/V7__fix_service_species.sql b/backend/src/main/resources/db/migration/V7__fix_service_species.sql deleted file mode 100644 index 4c22a1b0..00000000 --- a/backend/src/main/resources/db/migration/V7__fix_service_species.sql +++ /dev/null @@ -1,14 +0,0 @@ -DELETE FROM service_species WHERE serviceId = 2 AND species = 'Bird'; - -INSERT INTO service_species (serviceId, species) VALUES -(1, 'Guinea Pig'), -(1, 'Hamster'), -(1, 'Other'), -(2, 'Reptile'), -(2, 'Other'), -(3, 'Reptile'), -(3, 'Other'), -(4, 'Reptile'), -(4, 'Other'), -(5, 'Reptile'), -(5, 'Other'); diff --git a/backend/src/main/resources/db/migration/V8__seed_activity_logs.sql b/backend/src/main/resources/db/migration/V8__seed_activity_logs.sql deleted file mode 100644 index aa3fa6d1..00000000 --- a/backend/src/main/resources/db/migration/V8__seed_activity_logs.sql +++ /dev/null @@ -1,90 +0,0 @@ -INSERT INTO activityLog (userId, storeId, usernameSnapshot, fullNameSnapshot, roleSnapshot, storeNameSnapshot, activity, logTimestamp) VALUES -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 08:02:11'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-01-15 08:15:44'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-01-15 08:31:07'), -(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:00:00'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:04:22'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 10:12:33'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-01-15 10:18:05'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-01-15 10:25:50'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #12 | PUT /api/v1/appointments/12 → 200', '2026-01-16 11:05:30'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-17 07:58:44'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated pet #8 | PUT /api/v1/pets/8 → 200', '2026-01-17 08:10:19'), -(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 09:01:55'), -(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 14:30:22'), -(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-01-20 14:45:08'), -(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-22 08:55:00'), -(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated pet #14 | PUT /api/v1/pets/14 → 200', '2026-01-22 09:20:17'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-25 08:00:00'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-01-25 08:22:41'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new employee | POST /api/v1/employees → 201', '2026-01-25 09:05:14'), -(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-28 18:30:00'), -(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-01-28 18:35:12'), -(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-01-28 18:40:55'), -(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-03 09:00:00'), -(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-03 10:15:38'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-05 07:50:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Deleted pet #3 | DELETE /api/v1/pets/3 → 200', '2026-02-05 08:05:33'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #22 | PUT /api/v1/products/22 → 200', '2026-02-05 08:30:44'), -(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-07 12:00:00'), -(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-02-07 12:08:17'), -(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Applied a coupon to cart | POST /api/v1/cart/apply-coupon → 200', '2026-02-07 12:12:05'), -(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-07 12:20:30'), -(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-10 09:00:00'), -(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-02-10 09:30:22'), -(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Updated appointment #25 | PUT /api/v1/appointments/25 → 200', '2026-02-10 11:45:09'), -(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-14 15:00:00'), -(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-02-14 15:05:44'), -(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/5/messages → 201', '2026-02-14 15:08:22'), -(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-17 09:00:00'), -(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Uploaded image for pet #31 | POST /api/v1/pets/31/image → 200', '2026-02-17 09:25:11'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-20 08:00:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-02-20 08:20:35'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #18 | PUT /api/v1/users/18 → 200', '2026-02-20 09:10:00'), -(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-22 19:00:00'), -(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-02-22 19:15:40'), -(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-01 09:00:00'), -(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-01 10:30:15'), -(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated appointment #33 | PUT /api/v1/appointments/33 → 200', '2026-03-01 14:05:22'), -(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-05 11:00:00'), -(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-03-05 11:10:30'), -(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Started checkout | POST /api/v1/cart/checkout → 200', '2026-03-05 11:18:44'), -(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-05 11:22:17'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-08 08:00:00'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Deleted multiple pets | POST /api/v1/pets/bulk-delete → 200', '2026-03-08 08:30:00'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-10 09:00:00'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-10 10:45:33'), -(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-12 16:00:00'), -(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-03-12 16:05:22'), -(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-03-12 16:20:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-15 07:55:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-03-15 08:10:45'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #35 | PUT /api/v1/products/35 → 200', '2026-03-15 08:40:12'), -(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-18 09:00:00'), -(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated appointment #45 | PUT /api/v1/appointments/45 → 200', '2026-03-18 09:35:08'), -(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-20 20:00:00'), -(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-03-20 20:05:30'), -(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/9/messages → 201', '2026-03-20 20:08:11'), -(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-24 09:00:00'), -(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-03-24 09:20:44'), -(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-27 09:00:00'), -(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated appointment #52 | PUT /api/v1/appointments/52 → 200', '2026-03-27 10:10:19'), -(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-27 11:30:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-01 08:00:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-04-01 08:15:00'), -(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #25 | PUT /api/v1/users/25 → 200', '2026-04-01 09:00:22'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-05 10:00:00'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-05 10:15:00'), -(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-05 10:25:44'), -(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-07 09:00:00'), -(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated pet #47 | PUT /api/v1/pets/47 → 200', '2026-04-07 09:40:55'), -(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-09 17:00:00'), -(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-04-09 17:20:33'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-12 08:00:00'), -(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-04-12 08:25:18'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-14 09:00:00'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-14 10:55:30'), -(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #60 | PUT /api/v1/appointments/60 → 200', '2026-04-14 14:20:00'), -(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-15 11:00:00'), -(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-15 11:10:22'), -(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-15 11:30:00'); diff --git a/backend/src/main/resources/db/migration/V9__seed_recent_sales.sql b/backend/src/main/resources/db/migration/V9__seed_recent_sales.sql deleted file mode 100644 index 23695f6f..00000000 --- a/backend/src/main/resources/db/migration/V9__seed_recent_sales.sql +++ /dev/null @@ -1,51 +0,0 @@ -INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES -(134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'IN_STORE', NULL, NULL, 57.67, 0.00, 0.00, 5), -(135, '2026-04-10 11:30:00', 143.55, 'Card', 8, 2, 17, 0, NULL, 'ONLINE', NULL, NULL, 143.55, 0.00, 0.00, 14), -(136, '2026-04-10 14:45:00', 50.09, 'Cash', 12, 3, 18, 0, NULL, 'IN_STORE', NULL, NULL, 50.09, 0.00, 0.00, 5), -(137, '2026-04-11 10:00:00', 114.48, 'Card', 5, 1, 19, 0, NULL, 'ONLINE', NULL, NULL, 114.48, 0.00, 0.00, 11), -(138, '2026-04-11 13:20:00', 93.55, 'Cash', 9, 2, 20, 0, NULL, 'IN_STORE', NULL, NULL, 93.55, 0.00, 0.00, 9), -(139, '2026-04-12 09:45:00', 100.71, 'Card', 13, 3, 21, 0, NULL, 'ONLINE', NULL, NULL, 100.71, 0.00, 0.00, 10), -(140, '2026-04-12 11:00:00', 51.07, 'Cash', 6, 1, 22, 0, NULL, 'IN_STORE', NULL, NULL, 51.07, 0.00, 0.00, 5), -(141, '2026-04-12 15:30:00', 139.66, 'Card', 7, 2, 23, 0, NULL, 'ONLINE', NULL, NULL, 139.66, 0.00, 0.00, 13), -(142, '2026-04-13 09:00:00', 73.98, 'Cash', 14, 3, 24, 0, NULL, 'IN_STORE', NULL, NULL, 73.98, 0.00, 0.00, 7), -(143, '2026-04-13 12:15:00', 134.76, 'Card', 4, 1, 25, 0, NULL, 'ONLINE', NULL, NULL, 134.76, 0.00, 0.00, 13), -(144, '2026-04-14 10:30:00', 80.40, 'Cash', 10, 2, 26, 0, NULL, 'IN_STORE', NULL, NULL, 80.40, 0.00, 0.00, 8), -(145, '2026-04-14 14:00:00', 125.90, 'Card', 11, 3, 27, 0, NULL, 'ONLINE', NULL, NULL, 125.90, 0.00, 0.00, 12), -(146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'IN_STORE', NULL, NULL, 80.62, 0.00, 0.00, 8), -(147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'ONLINE', NULL, NULL, 141.28, 0.00, 0.00, 14), -(148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'IN_STORE', NULL, NULL, 97.85, 0.00, 0.00, 9), -(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8); - -INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES -(264, 134, 1, 2, 25.09), -(265, 134, 11, 1, 7.49), -(266, 135, 7, 2, 57.62), -(267, 135, 25, 1, 28.31), -(268, 136, 3, 1, 35.93), -(269, 136, 14, 1, 14.16), -(270, 137, 15, 3, 16.38), -(271, 137, 26, 2, 32.67), -(272, 138, 8, 1, 63.05), -(273, 138, 22, 2, 15.25), -(274, 139, 20, 2, 27.49), -(275, 139, 29, 1, 45.73), -(276, 140, 4, 1, 41.36), -(277, 140, 12, 1, 9.71), -(278, 141, 34, 2, 59.42), -(279, 141, 17, 1, 20.82), -(280, 142, 6, 1, 52.20), -(281, 142, 21, 2, 10.89), -(282, 143, 37, 1, 97.56), -(283, 143, 16, 2, 18.60), -(284, 144, 9, 1, 68.47), -(285, 144, 13, 1, 11.93), -(286, 145, 19, 3, 25.27), -(287, 145, 30, 1, 50.09), -(288, 146, 2, 2, 30.51), -(289, 146, 23, 1, 19.60), -(290, 147, 35, 1, 72.13), -(291, 147, 18, 3, 23.05), -(292, 148, 10, 1, 73.89), -(293, 148, 24, 1, 23.96), -(294, 149, 31, 2, 21.29), -(295, 149, 5, 1, 46.78); -- 2.49.1 From 2bbb722693e81eb26654763db475a5293043c79f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 17:57:22 -0600 Subject: [PATCH 05/42] fix seed data gaps --- .../resources/db/migration/V2__seed_data.sql | 86 +++++++++++++------ 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index befa56fa..33505a84 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -2259,27 +2259,27 @@ SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/stores/', '/store WHERE imageUrl LIKE 'https://images.petshop.local/stores/%'; INSERT IGNORE INTO appointment (appointmentId, serviceId, petId, customerId, storeId, employeeId, appointmentDate, appointmentTime, appointmentStatus) VALUES -(91, 1, NULL, 16, 1, 3, '2026-03-08', '09:00:00', 'COMPLETED'), -(92, 2, NULL, 17, 2, 8, '2026-03-10', '10:30:00', 'COMPLETED'), -(93, 3, NULL, 18, 3, 13, '2026-03-12', '13:00:00', 'MISSED'), -(94, 4, NULL, 19, 1, 6, '2026-03-14', '14:30:00', 'COMPLETED'), -(95, 5, NULL, 20, 2, 7, '2026-03-16', '09:00:00', 'COMPLETED'), -(96, 6, NULL, 21, 3, 12, '2026-03-18', '10:30:00', 'COMPLETED'), -(97, 7, NULL, 22, 1, 5, '2026-03-20', '13:00:00', 'CANCELLED'), -(98, 8, NULL, 23, 2, 10, '2026-03-22', '14:30:00', 'COMPLETED'), -(99, 1, NULL, 24, 3, 11, '2026-03-24', '09:00:00', 'COMPLETED'), -(100, 2, NULL, 25, 1, 4, '2026-03-26', '10:30:00', 'MISSED'), -(101, 3, NULL, 26, 2, 9, '2026-03-28', '13:00:00', 'COMPLETED'), -(102, 4, NULL, 27, 3, 14, '2026-03-30', '14:30:00', 'COMPLETED'), -(103, 5, NULL, 28, 1, 3, '2026-04-01', '09:00:00', 'COMPLETED'), -(104, 6, NULL, 29, 2, 8, '2026-04-03', '10:30:00', 'COMPLETED'), -(105, 7, NULL, 30, 3, 13, '2026-04-05', '13:00:00', 'MISSED'), -(106, 8, NULL, 31, 1, 6, '2026-04-07', '14:30:00', 'COMPLETED'), -(107, 1, NULL, 32, 2, 7, '2026-04-09', '09:00:00', 'COMPLETED'), -(108, 2, NULL, 33, 3, 12, '2026-04-11', '10:30:00', 'CANCELLED'), -(109, 3, NULL, 34, 1, 5, '2026-04-13', '13:00:00', 'COMPLETED'), -(110, 4, NULL, 35, 2, 10, '2026-04-15', '10:00:00', 'SCHEDULED'), -(111, 5, NULL, 36, 3, 11, '2026-04-16', '14:00:00', 'SCHEDULED'); +(91, 7, 37, 16, 1, 3, '2026-03-08', '09:00:00', 'COMPLETED'), +(92, 8, 38, 17, 2, 8, '2026-03-10', '10:30:00', 'COMPLETED'), +(93, 8, 39, 18, 3, 13, '2026-03-12', '13:00:00', 'MISSED'), +(94, 4, 40, 19, 1, 6, '2026-03-14', '14:30:00', 'COMPLETED'), +(95, 5, 41, 20, 2, 7, '2026-03-16', '09:00:00', 'COMPLETED'), +(96, 8, 42, 21, 3, 12, '2026-03-18', '10:30:00', 'COMPLETED'), +(97, 2, 43, 22, 1, 5, '2026-03-20', '13:00:00', 'CANCELLED'), +(98, 8, 44, 23, 2, 10, '2026-03-22', '14:30:00', 'COMPLETED'), +(99, 8, 45, 24, 3, 11, '2026-03-24', '09:00:00', 'COMPLETED'), +(100, 8, 46, 25, 1, 4, '2026-03-26', '10:30:00', 'MISSED'), +(101, 6, 47, 26, 2, 9, '2026-03-28', '13:00:00', 'COMPLETED'), +(102, 4, 48, 27, 3, 14, '2026-03-30', '14:30:00', 'COMPLETED'), +(103, 7, 49, 28, 1, 3, '2026-04-01', '09:00:00', 'COMPLETED'), +(104, 6, 50, 29, 2, 8, '2026-04-03', '10:30:00', 'COMPLETED'), +(105, 1, 51, 30, 3, 13, '2026-04-05', '13:00:00', 'MISSED'), +(106, 4, 52, 31, 1, 6, '2026-04-07', '14:30:00', 'COMPLETED'), +(107, 1, 53, 32, 2, 7, '2026-04-09', '09:00:00', 'COMPLETED'), +(108, 2, 54, 33, 3, 12, '2026-04-11', '10:30:00', 'CANCELLED'), +(109, 3, 55, 34, 1, 5, '2026-04-13', '13:00:00', 'COMPLETED'), +(110, 4, 56, 35, 2, 10, '2026-04-15', '10:00:00', 'SCHEDULED'), +(111, 5, 57, 36, 3, 11, '2026-04-16', '14:00:00', 'SCHEDULED'); INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES (111, '2026-03-08 09:15:00', 87.50, 'Cash', 3, 1, 3, 0, NULL, 'IN_STORE', NULL, NULL, 87.50, 0.00, 0.00, 8), @@ -2435,7 +2435,21 @@ INSERT INTO activityLog (userId, storeId, usernameSnapshot, fullNameSnapshot, ro (4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #60 | PUT /api/v1/appointments/60 → 200', '2026-04-14 14:20:00'), (25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-15 11:00:00'), (25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-15 11:10:22'), -(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-15 11:30:00'); +(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-15 11:30:00'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-16 09:00:00'), +(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-16 09:45:22'), +(26, NULL,'alex.baker', 'Alex Baker', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-16 14:00:00'), +(26, NULL,'alex.baker', 'Alex Baker', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-16 14:10:15'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-17 09:00:00'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated appointment #110 | PUT /api/v1/appointments/110 → 200', '2026-04-17 09:30:44'), +(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-17 10:45:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-18 08:00:00'), +(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #12 | PUT /api/v1/products/12 → 200', '2026-04-18 08:25:30'), +(27, NULL,'alex.hall', 'Alex Hall', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-18 16:00:00'), +(27, NULL,'alex.hall', 'Alex Hall', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-18 16:08:33'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-19 09:00:00'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-19 10:15:00'), +(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-04-19 11:00:22'); INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES (134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'IN_STORE', NULL, NULL, 57.67, 0.00, 0.00, 5), @@ -2453,7 +2467,15 @@ INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeI (146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'IN_STORE', NULL, NULL, 80.62, 0.00, 0.00, 8), (147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'ONLINE', NULL, NULL, 141.28, 0.00, 0.00, 14), (148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'IN_STORE', NULL, NULL, 97.85, 0.00, 0.00, 9), -(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8); +(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8), +(150, '2026-04-17 09:15:00', 112.38, 'Cash', 13, 3, 32, 0, NULL, 'IN_STORE', NULL, NULL, 112.38, 0.00, 0.00, 11), +(151, '2026-04-17 11:30:00', 67.49, 'Card', 5, 1, 33, 0, NULL, 'ONLINE', NULL, NULL, 67.49, 0.00, 0.00, 6), +(152, '2026-04-17 14:45:00', 158.20, 'Cash', 9, 2, 34, 0, NULL, 'IN_STORE', NULL, NULL, 158.20, 0.00, 0.00, 15), +(153, '2026-04-18 09:30:00', 84.76, 'Card', 14, 3, 35, 0, NULL, 'ONLINE', NULL, NULL, 84.76, 0.00, 0.00, 8), +(154, '2026-04-18 12:00:00', 203.15, 'Cash', 4, 1, 36, 0, NULL, 'IN_STORE', NULL, NULL, 203.15, 0.00, 0.00, 20), +(155, '2026-04-18 15:15:00', 45.93, 'Card', 7, 2, 37, 0, NULL, 'ONLINE', NULL, NULL, 45.93, 0.00, 0.00, 4), +(156, '2026-04-19 10:00:00', 129.84, 'Cash', 11, 3, 38, 0, NULL, 'IN_STORE', NULL, NULL, 129.84, 0.00, 0.00, 12), +(157, '2026-04-19 13:30:00', 76.50, 'Card', 6, 1, 39, 0, NULL, 'ONLINE', NULL, NULL, 76.50, 0.00, 0.00, 7); INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES (264, 134, 1, 2, 25.09), @@ -2487,4 +2509,20 @@ INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VA (292, 148, 10, 1, 73.89), (293, 148, 24, 1, 23.96), (294, 149, 31, 2, 21.29), -(295, 149, 5, 1, 46.78); +(295, 149, 5, 1, 46.78), +(296, 150, 20, 2, 27.49), +(297, 150, 33, 1, 57.40), +(298, 151, 2, 1, 30.51), +(299, 151, 16, 2, 18.49), +(300, 152, 34, 2, 59.42), +(301, 152, 11, 1, 39.36), +(302, 153, 23, 2, 19.60), +(303, 153, 9, 1, 45.56), +(304, 154, 37, 1, 97.56), +(305, 154, 28, 1, 105.59), +(306, 155, 26, 1, 32.67), +(307, 155, 12, 1, 13.26), +(308, 156, 19, 3, 25.27), +(309, 156, 30, 1, 54.03), +(310, 157, 6, 1, 52.20), +(311, 157, 14, 2, 12.15); -- 2.49.1 From ca87b2578cbc48e1c40d46f87b49c33390fcb4a5 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 18:26:16 -0600 Subject: [PATCH 06/42] fix validation bugs --- .../backend/controller/AuthController.java | 19 ++++++++-------- .../controller/UserAvatarController.java | 22 ++++++++++++------- .../backend/service/AppointmentService.java | 10 ++++----- .../backend/service/AvatarStorageService.java | 21 ++++++++++++++++-- .../petshop/backend/service/ChatService.java | 6 +++++ .../backend/service/CouponService.java | 4 ++++ .../main/resources/static/default-avatar.svg | 1 + 7 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 backend/src/main/resources/static/default-avatar.svg diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index a71c3777..eb91c6dc 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -309,17 +309,18 @@ public class AuthController { public ResponseEntity getAvatarFile() { User user = authHelper.getAuthenticatedUser(); - if (!avatarStorageService.hasAvatar(user)) { - return ResponseEntity.notFound().build(); + if (avatarStorageService.hasAvatar(user)) { + try { + Resource resource = avatarStorageService.loadAvatarResource(user); + MediaType mediaType = avatarStorageService.resolveMediaType(user); + return ResponseEntity.ok().contentType(mediaType).body(resource); + } catch (IllegalArgumentException ignored) { + } } - try { - Resource resource = avatarStorageService.loadAvatarResource(user); - MediaType mediaType = avatarStorageService.resolveMediaType(user); - return ResponseEntity.ok().contentType(mediaType).body(resource); - } catch (IllegalArgumentException ex) { - return ResponseEntity.notFound().build(); - } + return ResponseEntity.ok() + .contentType(MediaType.valueOf("image/svg+xml")) + .body(avatarStorageService.loadDefaultAvatarResource()); } @DeleteMapping("/me/avatar") diff --git a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java index 602cfe54..ac7d146c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java +++ b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java @@ -7,6 +7,7 @@ import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AvatarStorageService; import com.petshop.backend.util.ImageValidationUtil; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; @@ -42,18 +43,23 @@ public class UserAvatarController { @PreAuthorize("isAuthenticated()") public ResponseEntity getUserAvatarFile(@PathVariable Long userId) { User user = userRepository.findById(userId).orElse(null); - if (user == null || !avatarStorageService.hasAvatar(user)) { + if (user == null) { return ResponseEntity.notFound().build(); } - try { - Resource resource = avatarStorageService.loadAvatarResource(user); - return ResponseEntity.ok() - .contentType(avatarStorageService.resolveMediaType(user)) - .body(resource); - } catch (IllegalArgumentException ex) { - return ResponseEntity.notFound().build(); + if (avatarStorageService.hasAvatar(user)) { + try { + Resource resource = avatarStorageService.loadAvatarResource(user); + return ResponseEntity.ok() + .contentType(avatarStorageService.resolveMediaType(user)) + .body(resource); + } catch (IllegalArgumentException ignored) { + } } + + return ResponseEntity.ok() + .contentType(MediaType.valueOf("image/svg+xml")) + .body(avatarStorageService.loadDefaultAvatarResource()); } @PostMapping("/{userId}/avatar") diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index 5ffcc16d..832dc12e 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -143,7 +143,7 @@ public class AppointmentService { appointment.setEmployee(employee); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); - appointment.setAppointmentStatus(request.getAppointmentStatus()); + appointment.setAppointmentStatus("Scheduled"); appointment.setPet(pet); appointment = appointmentRepository.save(appointment); @@ -304,11 +304,9 @@ public class AppointmentService { } private void validateAppointmentRequest(AppointmentRequest request) { - if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) { - LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); - if (appointmentDateTime.isBefore(LocalDateTime.now())) { - throw new BusinessException("Booked appointments must be scheduled in the future"); - } + LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); + if (appointmentDateTime.isBefore(LocalDateTime.now())) { + throw new BusinessException("Appointments must be scheduled in the future"); } } diff --git a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java index 59442d4f..5ff61021 100644 --- a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java @@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile; import jakarta.annotation.PostConstruct; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -54,7 +55,11 @@ public class AvatarStorageService { public Resource loadAvatarResource(User user) { String filename = extractFilename(user.getAvatarUrl()); if (blobService.isEnabled()) { - return new ByteArrayResource(blobService.download(BLOB_CONTAINER, filename)); + try { + return new ByteArrayResource(blobService.download(BLOB_CONTAINER, filename)); + } catch (Exception ex) { + throw new IllegalArgumentException("Avatar file was not found"); + } } Path filePath = resolveStoredAvatarPath(user.getAvatarUrl()); if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { @@ -63,6 +68,18 @@ public class AvatarStorageService { return new PathResource(filePath); } + public Resource loadDefaultAvatarResource() { + InputStream is = getClass().getResourceAsStream("/static/default-avatar.svg"); + if (is == null) { + throw new IllegalStateException("Default avatar resource not found"); + } + try { + return new ByteArrayResource(is.readAllBytes()); + } catch (IOException e) { + throw new IllegalStateException("Failed to read default avatar", e); + } + } + public void deleteAvatar(User user) throws IOException { if (user.getAvatarUrl() == null || user.getAvatarUrl().isBlank()) return; if (blobService.isEnabled()) { @@ -80,7 +97,7 @@ public class AvatarStorageService { } public String toOwnerAvatarUrl(User user) { - return hasAvatar(user) ? "/api/v1/users/" + user.getId() + "/avatar/file" : null; + return "/api/v1/users/" + user.getId() + "/avatar/file"; } public String toStoredAvatarUrl(String avatarFilenamePath) { diff --git a/backend/src/main/java/com/petshop/backend/service/ChatService.java b/backend/src/main/java/com/petshop/backend/service/ChatService.java index a1442d41..bf519f23 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -146,6 +146,12 @@ public class ChatService { } } + boolean hasContent = request.getContent() != null && !request.getContent().isBlank(); + boolean hasAttachment = request.getAttachmentUrl() != null && !request.getAttachmentUrl().isBlank(); + if (!hasContent && !hasAttachment) { + throw new BusinessException("Message must have content or an attachment"); + } + ContentFilter.validate(request.getContent()); Message message = new Message(); diff --git a/backend/src/main/java/com/petshop/backend/service/CouponService.java b/backend/src/main/java/com/petshop/backend/service/CouponService.java index 9625cfed..18ff269c 100644 --- a/backend/src/main/java/com/petshop/backend/service/CouponService.java +++ b/backend/src/main/java/com/petshop/backend/service/CouponService.java @@ -89,6 +89,10 @@ public class CouponService { } private void updateCouponFields(Coupon coupon, CouponRequest request) { + if ("PERCENTAGE".equalsIgnoreCase(request.getDiscountType()) + && request.getDiscountValue().compareTo(new BigDecimal("100")) >= 0) { + throw new BusinessException("Percentage discount must be less than 100"); + } coupon.setCouponCode(request.getCouponCode()); coupon.setDiscountType(request.getDiscountType()); coupon.setDiscountValue(request.getDiscountValue()); diff --git a/backend/src/main/resources/static/default-avatar.svg b/backend/src/main/resources/static/default-avatar.svg new file mode 100644 index 00000000..abacf491 --- /dev/null +++ b/backend/src/main/resources/static/default-avatar.svg @@ -0,0 +1 @@ + -- 2.49.1 From 4aa0817000c322510083a143d666e78d0f44b293 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 18:17:42 -0600 Subject: [PATCH 07/42] fix profile placeholder flash --- android/app/src/main/res/layout/fragment_profile.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/res/layout/fragment_profile.xml b/android/app/src/main/res/layout/fragment_profile.xml index 34a6e33c..1de5db19 100644 --- a/android/app/src/main/res/layout/fragment_profile.xml +++ b/android/app/src/main/res/layout/fragment_profile.xml @@ -45,7 +45,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:text="First Last" + android:text="" android:textColor="@color/white" android:textSize="22sp" android:textStyle="bold" /> @@ -87,7 +87,7 @@ android:id="@+id/tvProfileEmail" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="No email loaded" + android:text="" android:textColor="@color/text_dark" android:textSize="16sp" /> @@ -133,7 +133,7 @@ android:id="@+id/tvProfilePhone" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="No phone loaded" + android:text="" android:textColor="@color/text_dark" android:textSize="16sp" /> @@ -178,7 +178,7 @@ android:id="@+id/tvProfileRole" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="No role loaded" + android:text="" android:textSize="16sp" android:textColor="@color/accent_coral"/> @@ -208,7 +208,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:visibility="gone" + android:visibility="visible" android:indeterminateTint="@color/accent_coral"/> \ No newline at end of file -- 2.49.1 From 7072a7a2751879cf5ac9bac09e09fbc8bf83b2a4 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 18:56:58 -0600 Subject: [PATCH 08/42] add project README --- README.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6575e1cd..959d9825 100644 --- a/README.md +++ b/README.md @@ -1 +1,113 @@ -# group-2-threaded-project-petshop \ No newline at end of file +# PetShop + +A pet store management application with a Spring Boot API serving three clients: a Next.js web app, a JavaFX desktop app, and an Android app. + +Handles product sales, pet adoption, appointment booking, real-time chat, AI assistance, payments (Stripe), email notifications (Resend), and file storage (Azure Blob). + +## Tech Stack + +| Layer | Technology | +|-------|------------| +| API | Java 25, Spring Boot 4, Spring Security (JWT), Hibernate | +| Database | MySQL 8.0, Flyway migrations | +| Web | Next.js 16, React 19, Tailwind CSS 4 | +| Desktop | JavaFX, Maven | +| Android | Kotlin, Hilt, Retrofit, CameraX | +| Infra | Docker, Azure Container Apps | + +## Project Structure + +``` +main/ + backend/ Spring Boot REST API + web/ Next.js frontend + desktop/ JavaFX desktop client + android/ Android mobile app +``` + +## Prerequisites + +- Java 25 +- Node.js 18+ +- Docker +- Maven +- Android Studio (for mobile) + +## Getting Started + +### 1. Start the database + +```sh +cd backend +docker compose -f docker-compose.dev.yml up -d +``` + +### 2. Configure the backend + +```sh +cd backend +cp .env.example .env +``` + +Fill in `.env` with your keys: + +``` +JWT_SECRET= +STRIPE_SECRET_KEY=sk_test_... +OPENROUTER_API_KEY=sk-or-v1-... +RESEND_API_KEY=re_... +RESEND_FROM=PetShop +``` + +### 3. Run the backend + +```sh +cd backend +mvn spring-boot:run +``` + +The API starts at `http://localhost:8080`. Flyway runs migrations and seeds data automatically on first boot. + +### 4. Run the web frontend + +```sh +cd web +cp .env.example .env.local +npm install +npm run dev +``` + +The web app starts at `http://localhost:3000`. + +### 5. Run the desktop client (optional) + +```sh +cd desktop +cp connectionpetstore.properties.example connectionpetstore.properties +mvn javafx:run +``` + +### 6. Run the Android app (optional) + +Open `android/` in Android Studio and run on an emulator or device. + +## API + +A Postman collection is available at `backend/postman/`. Key endpoint groups: + +- `/api/auth` -- registration, login, password reset +- `/api/products` -- catalog and inventory +- `/api/pets` -- listings and adoption +- `/api/appointments` -- booking +- `/api/cart`, `/api/sales`, `/api/refunds` -- transactions +- `/api/chat` -- messaging and AI assistant +- `/ws` -- WebSocket (STOMP) for real-time updates + +## Docker (full stack) + +```sh +cd backend +docker compose up --build -d +``` + +Starts the API and MySQL together. The web frontend has its own Dockerfile for independent deployment. -- 2.49.1 From c14413a634e3afa5b45bb886f824980db94a167e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 19:04:13 -0600 Subject: [PATCH 09/42] add client run instructions --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/README.md b/README.md index 959d9825..84c0f597 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,83 @@ docker compose up --build -d ``` Starts the API and MySQL together. The web frontend has its own Dockerfile for independent deployment. + +## Running the Web App + +Requires Node.js 18+. + +```sh +cd web +cp .env.example .env.local +npm install +npm run dev +``` + +Open `http://localhost:3000`. The app proxies API calls to the backend at `http://localhost:8080` by default. + +To point at a different backend, edit `BACKEND_URL` and `NEXT_PUBLIC_BACKEND_URL` in `.env.local`. + +For a production build: + +```sh +npm run build +npm run start +``` + +## Running the Desktop App (JavaFX) + +Requires IntelliJ IDEA and Java 25+. + +1. Open the `desktop/` directory in IntelliJ. +2. Copy `connectionpetstore.properties.example` to `connectionpetstore.properties` and edit it to match your database. The defaults expect the dev Docker database: + +``` +url=jdbc:mysql://127.0.0.1:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC +user=petshop +password=petshop +``` + +3. Open **View > Tool Windows > Maven** and click **Reload All Maven Projects**. +4. Expand **Plugins > javafx** and double-click **javafx:run**. + +Default accounts seeded on first run: + +| Role | Username | Password | +|------|----------|----------| +| Admin | `admin` | `admin123` | +| Staff | `staff` | `staff123` | + +## Running the Android App + +Requires Android Studio and the Android SDK (min API 24). + +1. Copy `local.properties.template` to `local.properties` and set `sdk.dir` to your Android SDK path. +2. Configure the backend URLs in `local.properties`: + +```properties +# Emulator — 10.0.2.2 maps to the host machine's localhost +petstore.backend.emulatorUrl=http\://10.0.2.2\:8080/ + +# Physical device — use the host machine's LAN IP +petstore.backend.deviceUrl=http\://192.168.x.x\:8080/ +``` + +3. Open the `android/` directory in Android Studio. +4. Sync Gradle, then run on an emulator or connected device. + +## Running the Backend + +Requires IntelliJ IDEA and Java 25+. + +1. Open the `backend/` directory in IntelliJ. +2. Copy `.env.example` to `.env` and fill in your API keys. +3. Start the database using Docker from IntelliJ's **Services** panel, or from a terminal: + +```sh +cd backend +docker compose -f docker-compose.dev.yml up -d +``` + +4. Run the `BackendApplication` main class from IntelliJ. + +The API starts at `http://localhost:8080`. Flyway runs migrations and seeds data automatically on first boot. -- 2.49.1 From c2474f941e4886b1a1dd13b2549da542cb4f8b83 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 19:05:07 -0600 Subject: [PATCH 10/42] configurable rate limiter --- .../java/com/petshop/backend/security/RateLimitFilter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java index 567d4219..31f2434f 100644 --- a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java +++ b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java @@ -10,6 +10,8 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.beans.factory.annotation.Value; + import java.io.IOException; import java.time.Duration; import java.util.Map; @@ -24,6 +26,9 @@ public class RateLimitFilter extends OncePerRequestFilter { "/api/v1/auth/reset-password", new int[]{10, 15} ); + @Value("${app.rate-limit-enabled:true}") + private boolean enabled; + private final RateLimiterService rateLimiterService; private final ApiErrorResponder apiErrorResponder; @@ -37,7 +42,7 @@ public class RateLimitFilter extends OncePerRequestFilter { @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { String path = request.getRequestURI(); - int[] rule = RULES.get(path); + int[] rule = enabled ? RULES.get(path) : null; if (rule != null) { String ip = extractIp(request); -- 2.49.1 From 52892a731c6a898c040889a87b47fc3f01031953 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:18:28 -0600 Subject: [PATCH 11/42] added comments to android --- .../activities/ForgotPasswordActivity.java | 7 ++ .../activities/HomeActivity.java | 4 +- .../activities/MainActivity.java | 5 +- .../adapters/ActivityLogAdapter.java | 27 +++++ .../adapters/AdoptionAdapter.java | 18 +++ .../adapters/AppointmentAdapter.java | 24 ++++ .../adapters/BlackTextArrayAdapter.java | 1 + .../petstoremobile/adapters/ChatAdapter.java | 18 +++ .../adapters/CouponAdapter.java | 24 ++++ .../adapters/CustomerAdapter.java | 25 +++++ .../adapters/EmployeeAdapter.java | 24 ++++ .../adapters/InventoryAdapter.java | 25 ++++- .../adapters/MessageAdapter.java | 66 ++++++++++- .../petstoremobile/adapters/PetAdapter.java | 36 +++++- .../adapters/ProductAdapter.java | 24 ++++ .../adapters/ProductSupplierAdapter.java | 21 ++++ .../adapters/PurchaseOrderAdapter.java | 15 +++ .../petstoremobile/adapters/SaleAdapter.java | 15 +++ .../adapters/ServiceAdapter.java | 22 +++- .../adapters/SupplierAdapter.java | 25 ++++- .../adapters/WhiteTextArrayAdapter.java | 5 +- .../petstoremobile/api/ActivityLogApi.java | 2 + .../petstoremobile/api/AdoptionApi.java | 7 ++ .../petstoremobile/api/AppointmentApi.java | 9 +- .../petstoremobile/api/CategoryApi.java | 2 + .../example/petstoremobile/api/ChatApi.java | 5 +- .../petstoremobile/api/CustomerApi.java | 7 ++ .../petstoremobile/api/EmployeeApi.java | 12 +- .../petstoremobile/api/InventoryApi.java | 12 +- .../petstoremobile/api/MessageApi.java | 6 +- .../example/petstoremobile/api/PetApi.java | 8 +- .../petstoremobile/api/ProductApi.java | 13 ++- .../api/ProductSupplierApi.java | 10 +- .../petstoremobile/api/PurchaseOrderApi.java | 3 + .../example/petstoremobile/api/RefundApi.java | 6 + .../example/petstoremobile/api/SaleApi.java | 4 + .../petstoremobile/api/ServiceApi.java | 2 +- .../example/petstoremobile/api/StoreApi.java | 4 + .../example/petstoremobile/api/UserApi.java | 3 + .../petstoremobile/api/auth/TokenManager.java | 1 + .../petstoremobile/dtos/ActivityLogDTO.java | 3 + .../petstoremobile/dtos/AdoptionDTO.java | 3 + .../petstoremobile/dtos/AppointmentDTO.java | 3 + .../example/petstoremobile/dtos/AuthDTO.java | 4 +- .../dtos/AvatarUploadResponse.java | 3 + .../dtos/BulkDeleteRequest.java | 3 + .../petstoremobile/dtos/CategoryDTO.java | 3 + .../petstoremobile/dtos/ConversationDTO.java | 3 + .../petstoremobile/dtos/CouponDTO.java | 3 + .../petstoremobile/dtos/CustomerDTO.java | 3 + .../petstoremobile/dtos/DropdownDTO.java | 3 + .../petstoremobile/dtos/EmployeeDTO.java | 3 + .../petstoremobile/dtos/ErrorResponse.java | 5 +- .../petstoremobile/dtos/InventoryDTO.java | 3 + .../petstoremobile/dtos/MessageDTO.java | 3 + .../petstoremobile/dtos/PageResponse.java | 4 +- .../example/petstoremobile/dtos/PetDTO.java | 3 + .../petstoremobile/dtos/ProductDTO.java | 3 + .../dtos/ProductSupplierDTO.java | 3 + .../petstoremobile/dtos/PurchaseOrderDTO.java | 3 + .../petstoremobile/dtos/RefundDTO.java | 3 + .../example/petstoremobile/dtos/SaleDTO.java | 3 + .../dtos/SendMessageRequest.java | 3 + .../petstoremobile/dtos/ServiceDTO.java | 3 + .../example/petstoremobile/dtos/StoreDTO.java | 3 + .../petstoremobile/dtos/SupplierDTO.java | 3 + .../dtos/UpdateConversationStatusRequest.java | 3 + .../example/petstoremobile/dtos/UserDTO.java | 3 + .../fragments/ChatFragment.java | 81 +++++++++++++ .../fragments/ListFragment.java | 7 +- .../fragments/ProfileFragment.java | 6 + .../listfragments/ActivityLogFragment.java | 27 +++++ .../listfragments/AdoptionFragment.java | 63 +++++++++++ .../listfragments/AnalyticsFragment.java | 61 +++++++++- .../listfragments/AppointmentFragment.java | 69 ++++++++++++ .../listfragments/CouponFragment.java | 39 +++++++ .../listfragments/CustomerFragment.java | 33 ++++++ .../listfragments/InventoryFragment.java | 51 +++++++++ .../fragments/listfragments/PetFragment.java | 60 ++++++++++ .../listfragments/ProductFragment.java | 42 +++++++ .../ProductSupplierFragment.java | 51 +++++++++ .../listfragments/PurchaseOrderFragment.java | 45 ++++++++ .../fragments/listfragments/SaleFragment.java | 54 +++++++++ .../listfragments/ServiceFragment.java | 39 +++++++ .../listfragments/StaffFragment.java | 36 ++++++ .../listfragments/SupplierFragment.java | 42 +++++++ .../AdoptionDetailFragment.java | 49 ++++++++ .../AppointmentDetailFragment.java | 64 +++++++++++ .../detailfragments/CouponDetailFragment.java | 21 ++++ .../CustomerDetailFragment.java | 30 +++++ .../InventoryDetailFragment.java | 48 ++++++++ .../ProductDetailFragment.java | 45 ++++++++ .../ProductSupplierDetailFragment.java | 39 +++++++ .../PurchaseOrderDetailFragment.java | 15 +++ .../detailfragments/RefundFragment.java | 53 ++++++++- .../detailfragments/SaleDetailFragment.java | 60 ++++++++++ .../ServiceDetailFragment.java | 39 +++++++ .../detailfragments/StaffDetailFragment.java | 39 +++++++ .../SupplierDetailFragment.java | 39 +++++++ .../PetProfileFragment.java | 27 +++++ .../example/petstoremobile/models/Chat.java | 3 + .../petstoremobile/models/Message.java | 3 + .../example/petstoremobile/models/Sale.java | 3 + .../repositories/ActivityLogRepository.java | 6 + .../repositories/AdoptionRepository.java | 3 + .../repositories/AppointmentRepository.java | 3 + .../repositories/AuthRepository.java | 3 + .../repositories/BaseRepository.java | 2 +- .../repositories/CategoryRepository.java | 3 + .../repositories/ChatRepository.java | 2 +- .../repositories/CouponRepository.java | 3 + .../repositories/CustomerRepository.java | 3 + .../repositories/EmployeeRepository.java | 3 + .../repositories/InventoryRepository.java | 3 + .../repositories/PetRepository.java | 3 + .../repositories/ProductRepository.java | 3 + .../ProductSupplierRepository.java | 3 + .../repositories/PurchaseOrderRepository.java | 3 + .../repositories/SaleRepository.java | 3 + .../repositories/ServiceRepository.java | 3 + .../repositories/StoreRepository.java | 3 + .../repositories/SupplierRepository.java | 3 + .../repositories/UserRepository.java | 3 + .../services/ChatNotificationService.java | 4 +- .../petstoremobile/utils/EventDecorator.java | 6 + .../petstoremobile/utils/FileUtils.java | 10 ++ .../petstoremobile/utils/SelectionHelper.java | 34 +++++- .../petstoremobile/utils/SpinnerUtils.java | 7 +- .../viewmodels/ActivityLogListViewModel.java | 39 +++++++ .../viewmodels/AdoptionDetailViewModel.java | 74 +++++++++++- .../viewmodels/AdoptionListViewModel.java | 24 ++++ .../viewmodels/AnalyticsViewModel.java | 73 ++++++++++++ .../AppointmentDetailViewModel.java | 4 +- .../viewmodels/AppointmentListViewModel.java | 24 ++++ .../viewmodels/AuthViewModel.java | 15 ++- .../viewmodels/ChatListViewModel.java | 54 +++++++++ .../viewmodels/CouponDetailViewModel.java | 18 +++ .../viewmodels/CouponListViewModel.java | 20 ++++ .../viewmodels/CustomerDetailViewModel.java | 9 ++ .../viewmodels/CustomerListViewModel.java | 26 +++++ .../viewmodels/InventoryDetailViewModel.java | 39 +++++++ .../viewmodels/InventoryListViewModel.java | 24 ++++ .../viewmodels/PetListViewModel.java | 37 ++++++ .../viewmodels/PetProfileViewModel.java | 9 ++ .../viewmodels/ProductDetailViewModel.java | 33 ++++++ .../viewmodels/ProductListViewModel.java | 21 ++++ .../ProductSupplierDetailViewModel.java | 43 +++++++ .../ProductSupplierListViewModel.java | 28 +++++ .../PurchaseOrderDetailViewModel.java | 3 + .../PurchaseOrderListViewModel.java | 21 ++++ .../viewmodels/RefundViewModel.java | 39 +++++++ .../viewmodels/SaleDetailViewModel.java | 106 ++++++++++++++++++ .../viewmodels/SaleListViewModel.java | 34 ++++++ .../viewmodels/ServiceDetailViewModel.java | 24 ++++ .../viewmodels/ServiceListViewModel.java | 17 +++ .../viewmodels/StaffDetailViewModel.java | 27 +++++ .../viewmodels/StaffListViewModel.java | 33 ++++++ .../viewmodels/SupplierDetailViewModel.java | 24 ++++ .../viewmodels/SupplierListViewModel.java | 17 +++ .../websocket/StompChatManager.java | 80 ++++++++++--- 160 files changed, 3098 insertions(+), 81 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/ForgotPasswordActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/ForgotPasswordActivity.java index 82ea9934..ef08f5ee 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/ForgotPasswordActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/ForgotPasswordActivity.java @@ -32,6 +32,9 @@ public class ForgotPasswordActivity extends AppCompatActivity { private ActivityForgotPasswordBinding binding; + /** + * Set the content view for forget password page + */ @Override protected void onCreate(Bundle savedInstanceState) { EdgeToEdge.enable(this); @@ -54,6 +57,10 @@ public class ForgotPasswordActivity extends AppCompatActivity { binding.btnBackToLogin.setOnClickListener(v -> finish()); } + /** + * A function to send a reset link to the given email address. + * Calls the forgotPassword endpoint. To send the reset link + * */ private void sendResetLink(String email) { binding.btnSubmit.setEnabled(false); diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java index 01b4ca24..9e5fd6d0 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/HomeActivity.java @@ -78,7 +78,7 @@ public class HomeActivity extends AppCompatActivity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - setIntent(intent); // Set the new intent so fragments can access updated extras + setIntent(intent); handleIntent(intent); } @@ -103,7 +103,7 @@ public class HomeActivity extends AppCompatActivity { } /** - * Requests POST_NOTIFICATIONS permission from the user if running on Android 13 and above. + * Requests for notification permission from the user if running on Android 13 and above. */ private void requestNotificationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java index 11925052..b7aa1f1b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java @@ -98,7 +98,7 @@ public class MainActivity extends AppCompatActivity { } /** - * Executes the login process using the AuthViewModel and handles the authentication response. + * Perform login process using the AuthViewModel and handles the authentication response. */ private void performLogin(String username, String password) { viewModel.login(username, password).observe(this, resource -> { @@ -112,6 +112,7 @@ public class MainActivity extends AppCompatActivity { case SUCCESS: if (resource.data != null) { String role = resource.data.getRole(); + //Check if role is staff/admin or customer if ("CUSTOMER".equalsIgnoreCase(role)) { UIUtils.setViewsEnabled(true, binding.btnLogin); binding.tvLoginStatus.setText("Customers are not allowed to log in"); @@ -132,7 +133,7 @@ public class MainActivity extends AppCompatActivity { } /** - * Retrieves the logged-in user's profile information to save their ID before navigating to the home screen. + * Retrieves the user's profile information to save their ID before navigating to the home screen. */ private void fetchUserIdAndNavigate() { viewModel.getMe().observe(this, resource -> { diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java index eb571ca2..ecd34994 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java @@ -25,10 +25,16 @@ public class ActivityLogAdapter extends RecyclerView.Adapter items; + /** + * Constructor for the ActivityLogAdapter. + */ public ActivityLogAdapter(List items) { this.items = items; } + /** + * Inflates the layout for an activity log item and creates a new ViewHolder. + */ @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -37,6 +43,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter adoptionList, OnAdoptionClickListener listener) { this.adoptionList = adoptionList; this.listener = listener; @@ -39,11 +42,17 @@ public class AdoptionAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Resets the selection state, deselecting all items. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); @@ -58,6 +67,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter appointmentList, OnAppointmentClickListener appointmentClickListener) { this.appointmentList = appointmentList; @@ -40,25 +43,40 @@ public class AppointmentAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Resets the selection state, deselecting all items. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } + /** + * ViewHolder class that holds references to the UI components for an appointment item. + */ public static class AppointmentViewHolder extends RecyclerView.ViewHolder { private final ItemAppointmentBinding binding; + /** + * Initializes the ViewHolder by finding the views within the item layout. + */ public AppointmentViewHolder(@NonNull ItemAppointmentBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for an appointment item and creates the ViewHolder. + */ @NonNull @Override public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -66,6 +84,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter extends ArrayAdapter { public BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) { super(context, resource, objects); diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ChatAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ChatAdapter.java index 972ac56d..329adfe8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ChatAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ChatAdapter.java @@ -20,11 +20,17 @@ public class ChatAdapter extends RecyclerView.Adapter chatList, OnChatClickListener listener) { this.chatList = chatList; this.listener = listener; } + /** + * Inflates the layout for a chat item and creates the ViewHolder. + */ @NonNull @Override public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -32,6 +38,9 @@ public class ChatAdapter extends RecyclerView.Adapter listener.onChatClick(chat)); } + /** + * Returns the total number of chat items in the list. + */ @Override public int getItemCount() { return chatList.size(); } + /** + * ViewHolder class that holds references to the UI components for a chat item. + */ public static class ChatViewHolder extends RecyclerView.ViewHolder { final ItemChatBinding binding; + /** + * Initializes the ViewHolder with the chat item's view binding. + */ public ChatViewHolder(@NonNull ItemChatBinding binding) { super(binding.getRoot()); this.binding = binding; diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/CouponAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/CouponAdapter.java index ff9bbe65..10994ddd 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/CouponAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/CouponAdapter.java @@ -30,11 +30,17 @@ public class CouponAdapter extends RecyclerView.Adapter coupons, OnCouponClickListener listener) { this.coupons = coupons; this.listener = listener; } + /** + * Inflates the layout for a coupon item and creates the ViewHolder. + */ @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -42,6 +48,9 @@ public class CouponAdapter extends RecyclerView.Adapter toggleSelection(coupon.getCouponId())); } + /** + * Toggles the selection state of a specific coupon by its ID. + */ private void toggleSelection(Long id) { if (selectedIds.contains(id)) { selectedIds.remove(id); @@ -105,6 +117,9 @@ public class CouponAdapter extends RecyclerView.Adapter getSelectedIds() { return selectedIds; } + /** + * Returns the total number of coupons in the list. + */ @Override public int getItemCount() { return coupons.size(); @@ -125,6 +146,9 @@ public class CouponAdapter extends RecyclerView.Adapter list, OnCustomerClickListener listener) { this.list = list; this.listener = listener; } + /** + * Sets the base URL for fetching customer profile images. + */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + + /** + * Sets the authentication token for fetching images. + */ public void setToken(String token) { this.token = token; } + /** + * ViewHolder class for customer items. + */ public static class CustomerViewHolder extends RecyclerView.ViewHolder { final ItemCustomerBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public CustomerViewHolder(@NonNull ItemCustomerBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for a customer item and creates the ViewHolder. + */ @NonNull @Override public CustomerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -49,6 +68,9 @@ public class CustomerAdapter extends RecyclerView.Adapter listener.onCustomerClick(position)); } + /** + * Returns the total number of customers in the list. + */ @Override public int getItemCount() { return list.size(); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/EmployeeAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/EmployeeAdapter.java index 8213dbd0..af8f99e5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/EmployeeAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/EmployeeAdapter.java @@ -25,28 +25,46 @@ public class EmployeeAdapter extends RecyclerView.Adapter list, OnEmployeeClickListener listener) { this.list = list; this.listener = listener; } + /** + * Sets the base URL for fetching employee profile images. + */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + /** + * Sets the authentication token for fetching images. + */ public void setToken(String token) { this.token = token; } + /** + * ViewHolder class for employee items. + */ public static class EmployeeViewHolder extends RecyclerView.ViewHolder { private final ItemEmployeeBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public EmployeeViewHolder(@NonNull ItemEmployeeBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for an employee item and creates the ViewHolder. + */ @NonNull @Override public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -55,6 +73,9 @@ public class EmployeeAdapter extends RecyclerView.Adapter listener.onEmployeeClick(position)); } + /** + * Returns the total number of employees in the list. + */ @Override public int getItemCount() { return list.size(); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java index 9fbc1481..a015bb97 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java @@ -27,6 +27,9 @@ public class InventoryAdapter extends RecyclerView.Adapter inventoryList, OnInventoryClickListener clickListener) { this.inventoryList = inventoryList; this.clickListener = clickListener; @@ -43,25 +46,40 @@ public class InventoryAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Resets the selection state, deselecting all items. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } + /** + * ViewHolder class that holds references to the UI components for an inventory item. + */ public static class InventoryViewHolder extends RecyclerView.ViewHolder { final ItemInventoryBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public InventoryViewHolder(@NonNull ItemInventoryBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for an inventory item and creates the ViewHolder. + */ @NonNull @Override public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -69,6 +87,9 @@ public class InventoryAdapter extends RecyclerView.Adapter messages, Long currentUserId) { this.messages = messages; this.currentUserId = currentUserId; setHasStableIds(true); } + /** + * Returns an ID for each message. + */ @Override public long getItemId(int position) { Message m = messages.get(position); return m.getId() != null ? m.getId() : position; } + /** + * Updates the current user's ID and refreshes the list. + */ public void setCurrentUserId(Long id) { this.currentUserId = id; notifyDataSetChanged(); } + /** + * Updates the staff ID to identify staff messages in the UI. + */ public void setStaffId(Long id) { this.staffId = id; notifyDataSetChanged(); } + /** + * Sets the authentication token. + */ public void setToken(String token) { this.token = token; } + /** + * Sets the base API URL. + */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + /** + * Sets a listener for clicks on message attachments. + */ public void setOnAttachmentClickListener(OnAttachmentClickListener listener) { this.attachmentClickListener = listener; } + /** + * Determines if a message is 'sent' or 'received' based on the sender's ID. + */ @Override public int getItemViewType(int position) { Message m = messages.get(position); @@ -80,6 +104,9 @@ public class MessageAdapter extends RecyclerView.Adapter i void onSelectionChanged(int selectedCount); } - //Constructor + /** + * Constructor for PetAdapter. + */ public PetAdapter(List petList, OnPetClickListener petClickListener) { this.petList = petList; this.petClickListener = petClickListener; @@ -48,35 +50,54 @@ public class PetAdapter extends RecyclerView.Adapter i }); } + /** + * Sets the base URL for fetching pet images from the server. + */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + /** + * Sets the authentication token + */ public void setToken(String token) { this.token = token; } + /** + * Returns a list of IDs for the currently selected pet items. + */ @Override public List getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Resets the selection state, deselecting all items. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } - // Get the controls of each row in recycler view + /** + * ViewHolder class that holds references to the UI components for a pet item. + */ public static class PetViewHolder extends RecyclerView.ViewHolder { private final ItemPetBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public PetViewHolder(@NonNull ItemPetBinding binding) { super(binding.getRoot()); this.binding = binding; } } - // Create a new row view + /** + * Inflates the layout for a pet item and creates the ViewHolder. + */ @NonNull @Override public PetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -84,7 +105,9 @@ public class PetAdapter extends RecyclerView.Adapter i return new PetViewHolder(binding); } - //populate the row with pet data + /** + * Binds pet data to the UI components. + */ @Override public void onBindViewHolder(@NonNull PetViewHolder holder, int position) { PetDTO pet = petList.get(position); @@ -103,7 +126,7 @@ public class PetAdapter extends RecyclerView.Adapter i binding.tvPetStatus.setText(pet.getPetStatus()); - //Set the status color depending on availability. If available, green, If Pending, yellow, otherwise red + //Set the status color depending on availability if (pet.getPetStatus() != null) { switch (pet.getPetStatus()) { case "Available": @@ -157,6 +180,9 @@ public class PetAdapter extends RecyclerView.Adapter i }); } + /** + * Returns the total number of pet items in the list. + */ @Override public int getItemCount() { return petList.size(); diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java index f5f897cc..9adea207 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java @@ -22,28 +22,46 @@ public class ProductAdapter extends RecyclerView.Adapter productList, OnProductClickListener listener) { this.productList = productList; this.listener = listener; } + /** + * Sets the base URL for fetching product images. + */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + /** + * Sets the authentication token + */ public void setToken(String token) { this.token = token; } + /** + * ViewHolder class for product items. + */ public static class ProductViewHolder extends RecyclerView.ViewHolder { final ItemProductBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public ProductViewHolder(@NonNull ItemProductBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for a product item and creates the ViewHolder. + */ @NonNull @Override public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -51,6 +69,9 @@ public class ProductAdapter extends RecyclerView.Adapter listener.onProductClick(position)); } + /** + * Returns the total number of product items in the list. + */ @Override public int getItemCount() { return productList.size(); } } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java index 231af741..c5c07e00 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java @@ -25,6 +25,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter list, OnProductSupplierClickListener listener) { this.list = list; this.listener = listener; @@ -41,25 +44,40 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Clears all selected items and exits selection mode. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } + /** + * ViewHolder for Product-Supplier relationship items. + */ public static class PSViewHolder extends RecyclerView.ViewHolder { final ItemProductSupplierBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public PSViewHolder(@NonNull ItemProductSupplierBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for a Product-Supplier item. + */ @NonNull @Override public PSViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -67,6 +85,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter list, OnPurchaseOrderClickListener listener) { this.list = list; this.listener = listener; } + /** + * ViewHolder for Purchase Order items. + */ public static class POViewHolder extends RecyclerView.ViewHolder { final ItemPurchaseOrderBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public POViewHolder(@NonNull ItemPurchaseOrderBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for a Purchase Order item. + */ @NonNull @Override public POViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -38,6 +50,9 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter saleList, OnSaleClickListener listener) { this.saleList = saleList; this.listener = listener; } + /** + * ViewHolder for Sale record items. + */ public static class SaleViewHolder extends RecyclerView.ViewHolder { final ItemSaleBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public SaleViewHolder(@NonNull ItemSaleBinding binding) { super(binding.getRoot()); this.binding = binding; } } + /** + * Inflates the layout for a Sale record item. + */ @NonNull @Override public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -41,6 +53,9 @@ public class SaleAdapter extends RecyclerView.Adapter serviceList, OnServiceClickListener clickListener) { this.serviceList = serviceList; this.clickListener = clickListener; @@ -47,29 +50,40 @@ public class ServiceAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Clears all selected items and exits selection mode. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } /** - * ViewHolder class for service items. + * ViewHolder for Service items. */ public static class ServiceViewHolder extends RecyclerView.ViewHolder { final ItemServiceBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public ServiceViewHolder(@NonNull ItemServiceBinding binding) { super(binding.getRoot()); this.binding = binding; } } - // Create a new row view + /** + * Inflates the layout for a Service item. + */ @NonNull @Override public ServiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -77,7 +91,9 @@ public class ServiceAdapter extends RecyclerView.Adapter supplierList, OnSupplierClickListener supplierClickListener) { this.supplierList = supplierList; this.supplierClickListener = supplierClickListener; @@ -43,27 +45,40 @@ public class SupplierAdapter extends RecyclerView.Adapter getSelectedKeys() { return selectionHelper.getSelectedKeys(); } + /** + * Clears all selected items and exits selection mode. + */ @Override public void clearSelection() { selectionHelper.clearSelection(); } - // Get the controls of each row in recycler view + /** + * ViewHolder for Supplier items. + */ public static class SupplierViewHolder extends RecyclerView.ViewHolder { final ItemSupplierBinding binding; + /** + * Initializes the ViewHolder with view binding. + */ public SupplierViewHolder(@NonNull ItemSupplierBinding binding) { super(binding.getRoot()); this.binding = binding; } } - // Create a new row view + /** + * Inflates the layout for a Supplier item. + */ @NonNull @Override public SupplierViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -71,7 +86,9 @@ public class SupplierAdapter extends RecyclerView.Adapter extends ArrayAdapter { public WhiteTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) { super(context, resource, objects); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java index 12ceb5bd..4ff8af6d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java @@ -8,8 +8,10 @@ import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; +// api calls to get activity logs public interface ActivityLogApi { + // Get activity logs with filters @GET("api/v1/activity-logs") Call> getActivityLogs( @Query("limit") int limit, diff --git a/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java b/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java index 6e1de73d..fa9114b5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java @@ -14,8 +14,10 @@ import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for adoptions public interface AdoptionApi { + // Get all adoptions with filters @GET("api/v1/adoptions") Call> getAllAdoptions( @Query("page") int page, @@ -27,18 +29,23 @@ public interface AdoptionApi { @Query("employeeId") Long employeeId, @Query("sort") String sort); + // Get adoption by id @GET("api/v1/adoptions/{id}") Call getAdoptionById(@Path("id") Long id); + // Create adoption @POST("api/v1/adoptions") Call createAdoption(@Body AdoptionDTO adoption); + // Update adoption @PUT("api/v1/adoptions/{id}") Call updateAdoption(@Path("id") Long id, @Body AdoptionDTO adoption); + // Delete adoption @DELETE("api/v1/adoptions/{id}") Call deleteAdoption(@Path("id") Long id); + // Bulk delete adoptions @HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true) Call bulkDeleteAdoptions(@Body BulkDeleteRequest request); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java b/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java index 5bc88643..f0752c00 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java @@ -14,8 +14,10 @@ import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for appointments public interface AppointmentApi { + // Get all appointments with filters @GET("api/v1/appointments") Call> getAllAppointments( @Query("page") int page, @@ -27,18 +29,23 @@ public interface AppointmentApi { @Query("employeeId") Long employeeId, @Query("sort") String sort); + // Get appointment by id @GET("api/v1/appointments/{id}") Call getAppointmentById(@Path("id") Long id); + // Create appointment @POST("api/v1/appointments") Call createAppointment(@Body AppointmentDTO appointment); + // Update appointment @PUT("api/v1/appointments/{id}") Call updateAppointment(@Path("id") Long id, @Body AppointmentDTO appointment); + // Delete appointment @DELETE("api/v1/appointments/{id}") Call deleteAppointment(@Path("id") Long id); + // Bulk delete appointments @HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true) Call bulkDeleteAppointments(@Body BulkDeleteRequest request); -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/api/CategoryApi.java b/android/app/src/main/java/com/example/petstoremobile/api/CategoryApi.java index d1084482..a82e0ab8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/CategoryApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/CategoryApi.java @@ -6,8 +6,10 @@ import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; +// api calls for categories public interface CategoryApi { + // Get all categories with pagination @GET("api/v1/categories") Call> getAllCategories( @Query("page") int page, diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ChatApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ChatApi.java index 7a4ab393..8a818a0a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ChatApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ChatApi.java @@ -12,15 +12,18 @@ import retrofit2.http.GET; import retrofit2.http.PUT; import retrofit2.http.Path; -//api calls to get conversations +// api calls for chat conversations public interface ChatApi { + // Get all conversations @GET("api/v1/chat/conversations") Call> getAllConversations(); + // Get conversation by id @GET("api/v1/chat/conversations/{conversationId}") Call getConversationById(@Path("conversationId") Long conversationId); + // Update conversation status @PUT("api/v1/chat/conversations/{conversationId}") Call updateConversationStatus(@Path("conversationId") Long conversationId, @Body UpdateConversationStatusRequest request); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/CustomerApi.java b/android/app/src/main/java/com/example/petstoremobile/api/CustomerApi.java index 55c51682..14be64e0 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/CustomerApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/CustomerApi.java @@ -15,23 +15,30 @@ import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for customers public interface CustomerApi { + // Get all customers with pagination @GET("api/v1/customers") Call> getAllCustomers(@Query("page") int page, @Query("size") int size); + // Get customer by id @GET("api/v1/customers/{customerId}") Call getCustomerById(@Path("customerId") Long customerId); + // Update customer @PUT("api/v1/customers/{customerId}") Call updateCustomer(@Path("customerId") Long customerId, @Body CustomerDTO customer); + // Delete customer @DELETE("api/v1/customers/{customerId}") Call deleteCustomer(@Path("customerId") Long customerId); + // Register customer @POST("api/v1/auth/register") Call registerCustomer(@Body CustomerDTO customer); + // Get customer dropdowns @GET("api/v1/dropdowns/customers") Call> getCustomerDropdowns(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/EmployeeApi.java b/android/app/src/main/java/com/example/petstoremobile/api/EmployeeApi.java index bbd873c3..33f08264 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/EmployeeApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/EmployeeApi.java @@ -5,28 +5,28 @@ import com.example.petstoremobile.dtos.PageResponse; import retrofit2.Call; import retrofit2.http.*; +// api calls for employees public interface EmployeeApi { + // Get all employees with pagination @GET("api/v1/employees") Call> getAllEmployees( @Query("page") int page, @Query("size") int size); + // Get employee by id @GET("api/v1/employees/{id}") Call getEmployeeById(@Path("id") Long id); + // Create employee @POST("api/v1/employees") Call createEmployee(@Body EmployeeDTO employee); + // Update employee @PUT("api/v1/employees/{id}") Call updateEmployee(@Path("id") Long id, @Body EmployeeDTO employee); + // Delete employee @DELETE("api/v1/employees/{id}") Call deleteEmployee(@Path("id") Long id); } - - - - - - diff --git a/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java b/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java index fa1e17b4..e9b3bb7c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java @@ -14,8 +14,10 @@ import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for inventory public interface InventoryApi { + // Get all inventory with filters @GET("api/v1/inventory") Call> getAllInventory( @Query("page") int page, @@ -24,23 +26,23 @@ public interface InventoryApi { @Query("storeId") Long storeId, @Query("sort") String sort); - // GET /api/v1/inventory/{id} + // Get inventory by id @GET("api/v1/inventory/{id}") Call getInventoryById(@Path("id") Long id); - // POST /api/v1/inventory + // Create inventory @POST("api/v1/inventory") Call createInventory(@Body InventoryDTO request); - // PUT /api/v1/inventory/{id} + // Update inventory @PUT("api/v1/inventory/{id}") Call updateInventory(@Path("id") Long id, @Body InventoryDTO request); - // DELETE /api/v1/inventory/{id} + // Delete inventory @DELETE("api/v1/inventory/{id}") Call deleteInventory(@Path("id") Long id); - // DELETE /api/v1/inventory (bulk delete) + // Bulk delete inventory @HTTP(method = "DELETE", path = "api/v1/inventory", hasBody = true) Call bulkDeleteInventory(@Body BulkDeleteRequest request); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java b/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java index e5504a03..cb921932 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java @@ -15,15 +15,18 @@ import retrofit2.http.Part; import retrofit2.http.Path; import retrofit2.http.Streaming; -//api calls to get and send messages +// api calls for messages public interface MessageApi { + // Get messages for a conversation @GET("api/v1/chat/conversations/{id}/messages") Call> getMessages(@Path("id") Long conversationId); + // Send a message @POST("api/v1/chat/conversations/{id}/messages") Call sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request); + // Send a message with attachment @Multipart @POST("api/v1/chat/conversations/{id}/attachments") Call sendMessageWithAttachment( @@ -32,6 +35,7 @@ public interface MessageApi { @Part MultipartBody.Part file ); + // Download attachment @GET("api/v1/chat/messages/{id}/attachment") @Streaming Call downloadAttachment(@Path("id") Long messageId); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java b/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java index b554166b..06e522f0 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java @@ -20,7 +20,7 @@ import retrofit2.http.Part; import retrofit2.http.Path; import retrofit2.http.Query; -//api calls to CRUD pets +// api calls to CRUD pets public interface PetApi { // endpoint for downloading the pet's image file String PET_IMAGE_PATH = "api/v1/pets/%d/image"; @@ -38,18 +38,23 @@ public interface PetApi { @Query("sort") String sort ); + // Get pets by customer id @GET("api/v1/dropdowns/customers/{customerId}/pets") Call> getCustomerPets(@Path("customerId") Long customerId); + // Get adoption pets @GET("api/v1/dropdowns/adoption-pets") Call> getAdoptionPets(@Query("storeId") Long storeId); + // Get pet dropdowns @GET("api/v1/dropdowns/pets") Call> getPetDropdowns(); + // Get pet species dropdowns @GET("api/v1/dropdowns/pet-species") Call> getPetSpeciesDropdowns(); + // Get pet breeds dropdowns @GET("api/v1/dropdowns/pet-breeds") Call> getPetBreedsDropdowns(@Query("species") String species); @@ -81,5 +86,4 @@ public interface PetApi { // Delete pet image @DELETE("api/v1/pets/{id}/image") Call deletePetImage(@Path("id") Long id); - } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java index 8aeb596e..88d7df0f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java @@ -9,9 +9,12 @@ import retrofit2.http.*; import java.util.List; +// api calls for products public interface ProductApi { + // endpoint for downloading the product's image file String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image"; + // Get all products with filters @GET("api/v1/products") Call> getAllProducts( @Query("q") String query, @@ -20,28 +23,36 @@ public interface ProductApi { @Query("size") int size, @Query("sort") String sort); + // Get product by id @GET("api/v1/products/{id}") Call getProductById(@Path("id") Long id); + // Create product @POST("api/v1/products") Call createProduct(@Body ProductDTO product); + // Update product @PUT("api/v1/products/{id}") Call updateProduct(@Path("id") Long id, @Body ProductDTO product); + // Delete product @DELETE("api/v1/products/{id}") Call deleteProduct(@Path("id") Long id); + // Upload product image @Multipart @POST("api/v1/products/{id}/image") Call uploadProductImage(@Path("id") Long id, @Part MultipartBody.Part image); + // Delete product image @DELETE("api/v1/products/{id}/image") Call deleteProductImage(@Path("id") Long id); + // Get product dropdowns @GET("api/v1/dropdowns/products") Call> getProductDropdowns(); + // Get category dropdowns @GET("api/v1/dropdowns/categories") Call> getCategoryDropdowns(); -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java index b4414be5..03f6d891 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java @@ -6,8 +6,10 @@ import com.example.petstoremobile.dtos.ProductSupplierDTO; import retrofit2.Call; import retrofit2.http.*; +// api calls for product-supplier relationships public interface ProductSupplierApi { + // Get all product-suppliers with filters @GET("api/v1/product-suppliers") Call> getAllProductSuppliers( @Query("page") int page, @@ -17,24 +19,30 @@ public interface ProductSupplierApi { @Query("supplierId") Long supplierId, @Query("sort") String sort); + // Get product-supplier by composite id @GET("api/v1/product-suppliers/{productId}/{supplierId}") Call getProductSupplierById( @Path("productId") Long productId, @Path("supplierId") Long supplierId); + // Create product-supplier @POST("api/v1/product-suppliers") Call createProductSupplier(@Body ProductSupplierDTO dto); + // Update product-supplier @PUT("api/v1/product-suppliers/{productId}/{supplierId}") Call updateProductSupplier( @Path("productId") Long productId, @Path("supplierId") Long supplierId, @Body ProductSupplierDTO dto); + // Delete product-supplier @DELETE("api/v1/product-suppliers/{productId}/{supplierId}") Call deleteProductSupplier( @Path("productId") Long productId, @Path("supplierId") Long supplierId); + + // Bulk delete product-suppliers @HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true) Call bulkDeleteProductSuppliers(@Body BulkDeleteRequest request); -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java index ebb99139..8746ed08 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java @@ -7,8 +7,10 @@ import retrofit2.http.GET; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for purchase orders public interface PurchaseOrderApi { + // Get all purchase orders with filters @GET("api/v1/purchase-orders") Call> getAllPurchaseOrders( @Query("page") int page, @@ -17,6 +19,7 @@ public interface PurchaseOrderApi { @Query("storeId") Long storeId, @Query("sort") String sort); + // Get purchase order by id @GET("api/v1/purchase-orders/{id}") Call getPurchaseOrderById(@Path("id") Long id); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/RefundApi.java b/android/app/src/main/java/com/example/petstoremobile/api/RefundApi.java index d7ba9575..6e3591bc 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/RefundApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/RefundApi.java @@ -6,20 +6,26 @@ import retrofit2.http.*; import java.util.List; +// api calls for refunds public interface RefundApi { + // Get all refunds @GET("api/v1/refunds") Call> getAllRefunds(); + // Get refund by id @GET("api/v1/refunds/{id}") Call getRefundById(@Path("id") Long id); + // Create refund @POST("api/v1/refunds") Call createRefund(@Body RefundDTO refund); + // Update refund @PUT("api/v1/refunds/{id}") Call updateRefund(@Path("id") Long id, @Body RefundDTO refund); + // Delete refund @DELETE("api/v1/refunds/{id}") Call deleteRefund(@Path("id") Long id); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java b/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java index cec94044..6d333dd5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java @@ -10,8 +10,10 @@ import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for sales public interface SaleApi { + // Get all sales with filters @GET("api/v1/sales") Call> getAllSales( @Query("page") int page, @@ -23,9 +25,11 @@ public interface SaleApi { @Query("customerId") Long customerId, @Query("sort") String sort); + // Get sale by id @GET("api/v1/sales/{id}") Call getSaleById(@Path("id") Long id); + // Create sale @POST("api/v1/sales") Call createSale(@Body SaleDTO sale); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java index 43014a53..9784524b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java @@ -14,7 +14,7 @@ import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; -//api calls to CRUD services +// api calls to CRUD services public interface ServiceApi { // Get all services @GET("api/v1/services") diff --git a/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java b/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java index f71b92b6..3370dd03 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java @@ -11,16 +11,20 @@ import retrofit2.http.GET; import retrofit2.http.Path; import retrofit2.http.Query; +// api calls for stores public interface StoreApi { + // Get all stores with pagination @GET("api/v1/stores") Call> getAllStores( @Query("page") int page, @Query("size") int size); + // Get store dropdowns @GET("api/v1/dropdowns/stores") Call> getStoreDropdowns(); + // Get employees of a specific store @GET("api/v1/dropdowns/stores/{storeId}/employees") Call> getStoreEmployees(@Path("storeId") Long storeId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java b/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java index 8346e221..dbc5640b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java @@ -7,9 +7,12 @@ import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; +// api calls for users public interface UserApi { + // endpoint for downloading the user's avatar file String AVATAR_PATH = "api/v1/users/%d/avatar/file"; + // Get all users with filters @GET("api/v1/users") Call> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size); } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java b/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java index dc6096de..f28395d3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/auth/TokenManager.java @@ -8,6 +8,7 @@ import javax.inject.Singleton; import dagger.hilt.android.qualifiers.ApplicationContext; +//Used to save and retrieve login data @Singleton public class TokenManager { private static final String TOKEN_KEY = "token"; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ActivityLogDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ActivityLogDTO.java index 2d4d0f6d..4a210a5d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ActivityLogDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ActivityLogDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for activity logs. + */ public class ActivityLogDTO { private Long logId; private String activity; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/AdoptionDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/AdoptionDTO.java index d48bc942..66459bee 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/AdoptionDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/AdoptionDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; +/** + * Data Transfer Object for pet adoptions. + */ public class AdoptionDTO { private Long adoptionId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/AppointmentDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/AppointmentDTO.java index 37f6640f..c793a142 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/AppointmentDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/AppointmentDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for appointments. + */ public class AppointmentDTO { private Long appointmentId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java index 6aecdbc3..563b1b5f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java @@ -1,6 +1,8 @@ package com.example.petstoremobile.dtos; -//Used to send login data to the backend +/** + * Data Transfer Object for authentication credentials. + */ public class AuthDTO { public static class LoginRequest { private String username; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java b/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java index 194be1f4..4495e05e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Response containing the URL of a newly uploaded avatar. + */ public class AvatarUploadResponse { private String avatarUrl; private String message; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java b/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java index e53c8369..0fd6ae5a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.util.List; +/** + * Request body for deleting multiple records at once. + */ public class BulkDeleteRequest { private List ids; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/CategoryDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/CategoryDTO.java index 6596845b..b8c84b7c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/CategoryDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/CategoryDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for product categories. + */ public class CategoryDTO { private Long categoryId; private String categoryName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ConversationDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ConversationDTO.java index 3a7ea42e..135bd84d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ConversationDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ConversationDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for chat conversations. + */ public class ConversationDTO { private Long id; private Long customerId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/CouponDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/CouponDTO.java index e2ecb2c4..647d9baf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/CouponDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/CouponDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; +/** + * Data Transfer Object for coupons. + */ public class CouponDTO { private Long couponId; private String couponCode; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java index c190342e..0872252c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import com.google.gson.annotations.SerializedName; +/** + * Data Transfer Object for customers. + */ public class CustomerDTO { @SerializedName("id") private Long customerId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java index 3174dae5..3f46d119 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for simple dropdown selection lists. + */ public class DropdownDTO { private Long id; private String label; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/EmployeeDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/EmployeeDTO.java index f29b4beb..f7288d73 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/EmployeeDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/EmployeeDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for employees. + */ public class EmployeeDTO { private Long id; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ErrorResponse.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ErrorResponse.java index ddb23eaa..56cc726c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ErrorResponse.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ErrorResponse.java @@ -1,7 +1,8 @@ package com.example.petstoremobile.dtos; -//Used to get messages of any errors from the backend - +/** + * Used to get messages of any errors from the backend. + */ public class ErrorResponse { private String message; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/InventoryDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/InventoryDTO.java index ddafd045..3fd6be57 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/InventoryDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/InventoryDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for inventory stock. + */ public class InventoryDTO { // Response fields (from backend InventoryResponse) private Long inventoryId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java index 8302ba86..17b54fa8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import com.google.gson.annotations.SerializedName; +/** + * Data Transfer Object for chat messages. + */ public class MessageDTO { @SerializedName("id") diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java b/android/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java index 7237105e..56558e15 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java @@ -2,7 +2,9 @@ package com.example.petstoremobile.dtos; import java.util.List; -//Used to get data from the API +/** + * Generic response wrapper for paginated API results. + */ public class PageResponse { private List content; private int totalPages; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java index 0e9a0b3f..3a3314d5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object representing a pet. + */ public class PetDTO { private Long petId; private String petName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java index 2b016b28..c95f0a32 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; +/** + * Data Transfer Object for products. + */ public class ProductDTO { private Long prodId; private String prodName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ProductSupplierDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductSupplierDTO.java index 887d29e1..e25f82ec 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ProductSupplierDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductSupplierDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; +/** + * Data Transfer Object for mapping products to suppliers. + */ public class ProductSupplierDTO { private Long productId; private String productName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/PurchaseOrderDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/PurchaseOrderDTO.java index 813633c9..eb760e59 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/PurchaseOrderDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/PurchaseOrderDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for purchase orders. + */ public class PurchaseOrderDTO { private Long purchaseOrderId; private Long supId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/RefundDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/RefundDTO.java index 5f47e1ce..dc3d2d67 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/RefundDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/RefundDTO.java @@ -2,6 +2,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; +/** + * Data Transfer Object for refund processing. + */ public class RefundDTO { // Response fields private Long id; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/SaleDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/SaleDTO.java index 9dc76029..d8501995 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/SaleDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/SaleDTO.java @@ -3,6 +3,9 @@ package com.example.petstoremobile.dtos; import java.math.BigDecimal; import java.util.List; +/** + * Data Transfer Object for sales transactions. + */ public class SaleDTO { // Response fields private Long saleId; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/SendMessageRequest.java b/android/app/src/main/java/com/example/petstoremobile/dtos/SendMessageRequest.java index 7a521de3..aab51a90 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/SendMessageRequest.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/SendMessageRequest.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Request body for sending a new chat message. + */ public class SendMessageRequest { private String content; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java index 56e44371..cfbacb8e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for services. + */ public class ServiceDTO { private Long serviceId; private String serviceName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/StoreDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/StoreDTO.java index da66f046..ba4d732a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/StoreDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/StoreDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for store information. + */ public class StoreDTO { private Long storeId; private String storeName; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java index e34816c1..e39d22c8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for suppliers. + */ public class SupplierDTO { private Long supId; private String supCompany; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/UpdateConversationStatusRequest.java b/android/app/src/main/java/com/example/petstoremobile/dtos/UpdateConversationStatusRequest.java index 4d7987e1..e0fe3fe2 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/UpdateConversationStatusRequest.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/UpdateConversationStatusRequest.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Request body for updating chat conversation status. + */ public class UpdateConversationStatusRequest { private String status; diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/UserDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/UserDTO.java index 2ab9902e..05866080 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/UserDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/UserDTO.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.dtos; +/** + * Data Transfer Object for user account details. + */ public class UserDTO { private Long id; private String username; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java index 88bb0f09..83678a68 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java @@ -65,6 +65,9 @@ import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; +/** + * Fragment for handling customer support chat. + */ @AndroidEntryPoint public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener, StompChatManager.ConversationListener, StompChatManager.ConnectionListener { @@ -90,6 +93,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private StompChatManager stompChatManager; private ActivityResultLauncher attachmentLauncher; + /** + * Initializes the view model and attachment launcher. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -105,6 +111,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis ); } + /** + * Inflates the layout and sets up UI event listeners. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -137,6 +146,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis return binding.getRoot(); } + /** + * Sets up the logic to open and close the chat drawer. + */ private void setupDrawerToggles() { binding.headerActiveChats.setOnClickListener(v -> { if (binding.rvActiveChats.getVisibility() == View.VISIBLE) { @@ -159,6 +171,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Configures the adapters and layout managers for chat lists and message history. + */ private void setupRecyclerViews() { activeChatAdapter = new ChatAdapter(activeChatList, this); binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext())); @@ -187,6 +202,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis setConversationActive(false, null); } + /** + * Displays a full-screen image preview for message attachments. + */ private void showFullScreenImage(Message message) { if (baseUrl == null || message.getId() == null) return; @@ -208,6 +226,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis dialog.show(); } + /** + * Initiates the download process for a message attachment. + */ private void downloadFile(Message message) { if (message.getId() == null) return; @@ -227,6 +248,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Saves the downloaded file body to the device's downloads folder. + */ private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) { android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper()); new Thread(() -> { @@ -270,6 +294,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }).start(); } + /** + * Observes LiveData from the ViewModel to update chat lists and messages. + */ private void observeViewModel() { viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> { activeChatList.clear(); @@ -300,12 +327,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading); } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Updates the chat header and input state if the active conversation changes. + */ private void updateTitleAndStateIfActive(List list) { if (activeConversationId != null) { for (Chat chat : list) { @@ -318,6 +351,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis } } + /** + * Loads initial chat data and establishes WebSocket connection. + */ private void loadInitialData() { String token = tokenManager.getToken(); Long currentUserId = tokenManager.getUserId(); @@ -353,6 +389,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis } } + /** + * Handles clicks on a chat from the drawer to switch the active conversation. + */ @Override public void onChatClick(Chat chat) { activeConversationId = Long.parseLong(chat.getChatId()); @@ -368,6 +407,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis viewModel.loadMessageHistory(activeConversationId); } + /** + * Closes the active chat conversation. + */ private void closeChat() { if (activeConversationId == null) return; @@ -392,6 +434,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Sends a text message to the active conversation. + */ private void sendMessage() { if (activeConversationId == null) return; String text = binding.etMessage.getText().toString().trim(); @@ -408,12 +453,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Opens a file picker to select an attachment. + */ private void selectAttachment() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); attachmentLauncher.launch(intent); } + /** + * Displays a preview of the selected attachment. + */ private void showAttachmentPreview(Uri uri) { pendingAttachmentUri = uri; binding.layoutAttachmentPreview.setVisibility(View.VISIBLE); @@ -427,11 +478,17 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis } } + /** + * Removes the currently selected attachment from the preview. + */ private void removeAttachment() { pendingAttachmentUri = null; binding.layoutAttachmentPreview.setVisibility(View.GONE); } + /** + * Sends a message with a file attachment. + */ private void sendWithAttachment(Uri uri) { if (activeConversationId == null) return; @@ -468,6 +525,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Callback for when a new message is received through the WebSocket. + */ @Override public void onMessageReceived(MessageDTO dto) { requireActivity().runOnUiThread(() -> { @@ -478,6 +538,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Callback for when a conversation's status or last message is updated. + */ @Override public void onConversationUpdated(ConversationDTO dto) { requireActivity().runOnUiThread(() -> { @@ -489,6 +552,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Callback for when the WebSocket connection is successfully opened. + */ @Override public void onSocketOpened() { if (!isAdded()) return; @@ -498,12 +564,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Callback for when the WebSocket connection is closed. + */ @Override public void onSocketClosed() { if (!isAdded()) return; requireActivity().runOnUiThread(viewModel::loadConversations); } + /** + * Callback for when a WebSocket error occurs. + */ @Override public void onSocketError() { if (!isAdded()) return; @@ -513,6 +585,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + /** + * Scrolls the message list to the most recent message. + */ private void scrollToBottom() { if (!messageList.isEmpty()) { binding.rvMessages.post(() -> @@ -520,6 +595,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis } } + /** + * Updates the UI state based on whether a conversation is active and its status. + */ private void setConversationActive(boolean active, String status) { boolean isClosed = "CLOSED".equalsIgnoreCase(status); UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach); @@ -541,6 +619,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis } } + /** + * Cleans up resources and disconnects the WebSocket when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java index 6f2c643b..8c3f3c2e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java @@ -23,7 +23,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; -//The Fragment for the displaying the list of entities to be viewed +/** + * Fragment that serves as a container for various list-based screens, providing a navigation drawer. + */ @AndroidEntryPoint public class ListFragment extends Fragment { @@ -97,6 +99,9 @@ public class ListFragment extends Fragment { return binding.getRoot(); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java index 7f8fc039..fb1decb7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java @@ -173,12 +173,18 @@ public class ProfileFragment extends Fragment { return binding.getRoot(); } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java index f369d383..6dab8ec3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java @@ -34,6 +34,9 @@ import java.util.List; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for viewing application activity logs with various filtering options. + */ @AndroidEntryPoint public class ActivityLogFragment extends Fragment { private FragmentActivityLogBinding binding; @@ -46,6 +49,9 @@ public class ActivityLogFragment extends Fragment { @Inject TokenManager tokenManager; + /** + * Inflates the layout, checks for admin access, and initializes ViewModel and UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -69,12 +75,18 @@ public class ActivityLogFragment extends Fragment { return binding.getRoot(); } + /** + * Triggers initial data loading after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel.loadInitialData(); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new ActivityLogAdapter(logList); binding.recyclerViewActivityLog.setLayoutManager(new LinearLayoutManager(getContext())); @@ -97,6 +109,9 @@ public class ActivityLogFragment extends Fragment { }); } + /** + * Sets up filters for logs, including search, role, store, and date range. + */ private void setupFilters() { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchLog, binding.spinnerRoleFilter, binding.spinnerStoreFilter); @@ -123,6 +138,9 @@ public class ActivityLogFragment extends Fragment { }); } + /** + * Displays a date picker dialog and updates the selected start or end date. + */ private void showDatePicker(boolean isStart) { Calendar cal = Calendar.getInstance(); new DatePickerDialog(requireContext(), (view, year, month, day) -> { @@ -141,12 +159,18 @@ public class ActivityLogFragment extends Fragment { }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show(); } + /** + * Handles store selection from the filter spinner. + */ private void onStoreSelected() { int pos = binding.spinnerStoreFilter.getSelectedItemPosition(); Long storeId = (pos > 0 && !storeList.isEmpty()) ? storeList.get(pos - 1).getId() : null; viewModel.setStoreFilter(storeId); } + /** + * Observes the ViewModel for log list updates, store options, and loading status. + */ private void observeViewModel() { viewModel.getLogs().observe(getViewLifecycleOwner(), list -> { logList.clear(); @@ -164,6 +188,9 @@ public class ActivityLogFragment extends Fragment { binding.swipeRefreshActivityLog.setRefreshing(loading)); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java index a6ba396e..fd4209f1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java @@ -44,6 +44,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of adoptions with a calendar view and filtering. + */ @AndroidEntryPoint public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener { @@ -58,12 +61,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop @Inject TokenManager tokenManager; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class); } + /** + * Inflates the layout and sets up UI components, calendar, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -88,6 +97,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop return binding.getRoot(); } + /** + * Observes the ViewModel for adoption list, stores, and loading status. + */ private void observeViewModel() { viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> { adoptionList.clear(); @@ -106,6 +118,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop }); } + /** + * Configures the bulk delete handler for multiple adoption record deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -119,6 +134,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop ); } + /** + * Reloads adoption data and stores when the fragment resumes. + */ @Override public void onResume() { super.onResume(); @@ -126,6 +144,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop if (!isStaff()) viewModel.loadStores(); } + /** + * Toggles between month and week display modes for the calendar. + */ private void toggleCalendarMode() { isMonthMode = !isMonthMode; binding.calendarViewAdoption.state().edit() @@ -133,6 +154,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop .commit(); } + /** + * Sets up the filter visibility toggle, considering user roles. + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption, @@ -144,10 +168,16 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop } } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Configures the calendar view for date-based filtering. + */ private void setupCalendar() { binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> { if (selected) { @@ -164,6 +194,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop }); } + /** + * Updates calendar decorators to highlight dates with adoptions. + */ private void updateCalendarDecorators() { HashSet datesWithAdoptions = new HashSet<>(); for (AdoptionDTO adoption : adoptionList) { @@ -184,6 +217,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions)); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new AdoptionAdapter(adoptionList, this); binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext())); @@ -206,23 +242,38 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop }); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchAdoption, () -> loadAdoptions(true)); } + /** + * Configures the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Completed", "Pending", "Missed", "Cancelled"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, () -> loadAdoptions(true)); } + /** + * Configures the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, () -> loadAdoptions(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshAdoption.setOnRefreshListener(() -> loadAdoptions(true)); } + /** + * Loads adoption data based on current filters, search query, and selected date. + */ private void loadAdoptions(boolean reset) { String query = binding.etSearchAdoption.getText().toString().trim(); String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses"; @@ -250,6 +301,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop viewModel.loadAdoptions(reset, query, status, storeId, selectedDateString, null); } + /** + * Navigates to the adoption detail screen. + */ private void openDetail(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -259,9 +313,15 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args); } + /** + * Handles adoption item clicks by opening details. + */ @Override public void onAdoptionClick(int position) { openDetail(position); } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int selectedCount) { if (bulkDeleteHandler != null) { @@ -269,6 +329,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop } } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java index 89aeb098..e493b0e3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java @@ -18,6 +18,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; +/** + * Fragment for displaying business analytics, including revenue, transactions, and product performance. + */ @AndroidEntryPoint public class AnalyticsFragment extends Fragment { @@ -31,6 +34,9 @@ public class AnalyticsFragment extends Fragment { private static final String[] TOP_N_OPTIONS = {"5", "10", "15", "20"}; private static final int[] TOP_N_VALUES = { 5, 10, 15, 20 }; + /** + * Inflates the layout, initializes ViewModel, and sets up UI components and filters. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -51,6 +57,9 @@ public class AnalyticsFragment extends Fragment { private static final int COLOR_SELECTED = 0xFF4ECDC4; private static final int COLOR_UNSELECTED = 0xFFCBD5E1; + /** + * Configures the view mode toggle buttons (My Analytics vs Store Analytics). + */ private void setupViewModeToggle() { updateViewModeButtonStyles(viewModel.getViewMode()); @@ -67,6 +76,9 @@ public class AnalyticsFragment extends Fragment { }); } + /** + * Updates the styles of the view mode buttons based on the selected mode. + */ private void updateViewModeButtonStyles(String mode) { binding.btnMyAnalytics.setBackgroundTintList( android.content.res.ColorStateList.valueOf(mode.equals("mine") ? COLOR_SELECTED : COLOR_UNSELECTED)); @@ -74,6 +86,9 @@ public class AnalyticsFragment extends Fragment { android.content.res.ColorStateList.valueOf(mode.equals("store") ? COLOR_SELECTED : COLOR_UNSELECTED)); } + /** + * Updates the visibility of the store filter based on the user's role and selected view mode. + */ private void updateStoreFilterVisibility(String mode) { boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole()); int vis = (isAdmin && mode.equals("store")) ? View.VISIBLE : View.GONE; @@ -81,8 +96,10 @@ public class AnalyticsFragment extends Fragment { binding.spinnerFilterStore.setVisibility(vis); } - // Filter Panel + /** + * Configures the filter panel, including date pickers, presets, and action buttons. + */ private void setupFilterPanel() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS); @@ -111,12 +128,18 @@ public class AnalyticsFragment extends Fragment { binding.btnFilterReset.setOnClickListener(v -> resetFilters()); } + /** + * Toggles the visibility of the filter content section. + */ private void toggleFilters() { filtersExpanded = !filtersExpanded; binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE); binding.tvFilterToggleIcon.setText(filtersExpanded ? "▲" : "▼"); } + /** + * Applies a date range preset to the filter fields. + */ private void applyPreset(int startOffset, int endOffset) { binding.etFilterStartDate.setText(getDateString(startOffset)); binding.etFilterEndDate.setText(getDateString(endOffset)); @@ -124,6 +147,9 @@ public class AnalyticsFragment extends Fragment { applyFiltersFromUI(); } + /** + * Reads filter values from the UI and applies them to the ViewModel. + */ private void applyFiltersFromUI() { AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState(); filter.startDate = binding.etFilterStartDate.getText().toString().trim(); @@ -142,6 +168,9 @@ public class AnalyticsFragment extends Fragment { viewModel.applyFilter(filter); } + /** + * Resets all filters to their default values. + */ private void resetFilters() { binding.etFilterStartDate.setText(""); binding.etFilterEndDate.setText(""); @@ -152,6 +181,9 @@ public class AnalyticsFragment extends Fragment { viewModel.resetFilter(); } + /** + * Updates the text summary of the currently selected date range. + */ private void updateFilterSummary() { String start = binding.etFilterStartDate.getText().toString().trim(); String end = binding.etFilterEndDate.getText().toString().trim(); @@ -166,10 +198,16 @@ public class AnalyticsFragment extends Fragment { } } + /** + * Formats a date string into a shorter version for display. + */ private String shortDate(String date) { return (date != null && date.length() >= 10) ? date.substring(5) : date; } + /** + * Returns a formatted date string for a given day. + */ private String getDateString(int offsetDays) { Calendar c = Calendar.getInstance(); c.add(Calendar.DAY_OF_YEAR, offsetDays); @@ -177,8 +215,10 @@ public class AnalyticsFragment extends Fragment { c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH)); } - // ViewModel Observation + /** + * Observes the ViewModel for analytics data, loading status, errors, and filter options. + */ private void observeViewModel() { viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay); @@ -216,14 +256,19 @@ public class AnalyticsFragment extends Fragment { }); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - // Display + /** + * Computes and displays analytics data in summary cards and bar charts. + */ private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) { if (data == null) return; @@ -312,8 +357,10 @@ public class AnalyticsFragment extends Fragment { } } - // Chart Helpers + /** + * Dynamically adds a bar chart row to a given layout container. + */ private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) { if (getContext() == null) return; LinearLayout row = new LinearLayout(getContext()); @@ -359,6 +406,9 @@ public class AnalyticsFragment extends Fragment { parent.addView(row); } + /** + * Adds an empty message row to a given layout container. + */ private void addEmptyRow(LinearLayout parent, String message) { if (getContext() == null) return; TextView tv = new TextView(getContext()); @@ -368,6 +418,9 @@ public class AnalyticsFragment extends Fragment { parent.addView(tv); } + /** + * Displays an error message and updates UI to reflect the error state. + */ private void showError(String msg) { if (getContext() == null || binding == null) return; binding.tvTotalRevenue.setText("Error"); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java index b8196775..99530081 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java @@ -45,6 +45,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of appointments with calendar integration and filtering. + */ @AndroidEntryPoint public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener { @@ -63,6 +66,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private Long currentUserId = null; private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + /** + * Initializes ViewModels for appointment and authentication data. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -70,6 +76,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. authViewModel = new ViewModelProvider(this).get(AuthViewModel.class); } + /** + * Inflates the layout and sets up UI components, calendar, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -97,6 +106,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. return binding.getRoot(); } + /** + * Observes the ViewModel for appointment list, stores, and loading status. + */ private void observeViewModel() { viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> { appointmentList.clear(); @@ -115,6 +127,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. }); } + /** + * Configures the bulk delete handler for multiple appointment deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -128,6 +143,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. ); } + /** + * Reloads appointment data and stores when the fragment resumes. + */ @Override public void onResume() { super.onResume(); @@ -135,6 +153,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. if (!isStaff()) viewModel.loadStores(); } + /** + * Toggles between month and week display modes for the calendar. + */ private void toggleCalendarMode() { isMonthMode = !isMonthMode; binding.calendarView.state().edit() @@ -142,12 +163,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. .commit(); } + /** + * Sets up the "My Appointments" filter button. + */ private void setupMyAppointmentFilter() { binding.btnMyAppointments.setOnClickListener(v -> { loadAppointmentData(true); }); } + /** + * Loads information about the currently logged-in user. + */ private void loadCurrentUserInfo() { authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -156,6 +183,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. }); } + /** + * Sets up the filter visibility toggle. + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment, binding.spinnerStatus); @@ -166,6 +196,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. } } + /** + * Configures the calendar view for date-based filtering. + */ private void setupCalendar() { binding.calendarView.setOnDateChangedListener((widget, date, selected) -> { if (selected) { @@ -182,6 +215,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. }); } + /** + * Updates calendar decorators to highlight dates with appointments. + */ private void updateCalendarDecorators() { HashSet datesWithAppointments = new HashSet<>(); for (AppointmentDTO appointment : appointmentList) { @@ -200,23 +236,38 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments)); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchAppointment, () -> loadAppointmentData(true)); } + /** + * Configures the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadAppointmentData(true)); } + /** + * Configures the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadAppointmentData(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshAppointment.setOnRefreshListener(() -> loadAppointmentData(true)); } + /** + * Navigates to the appointment detail screen. + */ private void openAppointmentDetails(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -226,11 +277,17 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args); } + /** + * Handles appointment item clicks by opening details. + */ @Override public void onAppointmentClick(int position) { openAppointmentDetails(position); } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int count) { if (bulkDeleteHandler != null) { @@ -238,10 +295,16 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. } } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Loads appointment data based on current filters, search query, and selected date. + */ private void loadAppointmentData(boolean reset) { String query = binding.etSearchAppointment.getText().toString().trim(); String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses"; @@ -274,6 +337,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. viewModel.loadAppointments(reset, query, status, storeId, selectedDateString, employeeId); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new AppointmentAdapter(appointmentList, this); binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext())); @@ -296,6 +362,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. }); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CouponFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CouponFragment.java index c0beebfe..9bc754b4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CouponFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CouponFragment.java @@ -27,6 +27,9 @@ import java.util.List; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of coupons. + */ @AndroidEntryPoint public class CouponFragment extends Fragment implements CouponAdapter.OnCouponClickListener { private FragmentCouponBinding binding; @@ -34,6 +37,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl private CouponAdapter adapter; private final List couponList = new ArrayList<>(); + /** + * Inflates the layout, initializes ViewModel, and sets up UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -59,6 +65,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl return binding.getRoot(); } + /** + * Observes the ViewModel for coupon list updates and loading status. + */ private void observeViewModel() { viewModel.getCoupons().observe(getViewLifecycleOwner(), list -> { couponList.clear(); @@ -71,6 +80,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl }); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new CouponAdapter(couponList, this); binding.recyclerViewCoupon.setLayoutManager(new LinearLayoutManager(getContext())); @@ -93,24 +105,39 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl }); } + /** + * Configures the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Active", "Inactive"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCoupon, statuses, () -> applyFilters(true)); } + /** + * Configures the discount type filter spinner. + */ private void setupTypeFilter() { String[] types = {"All Types", "FIXED", "PERCENT"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerTypeCoupon, types, () -> applyFilters(true)); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchCoupon, () -> applyFilters(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshCoupon.setOnRefreshListener(() -> applyFilters(true)); } + /** + * Applies filters and loads the coupon list. + */ private void applyFilters(boolean reset) { String statusStr = binding.spinnerStatusCoupon.getSelectedItem() != null ? binding.spinnerStatusCoupon.getSelectedItem().toString() : "All Statuses"; @@ -125,22 +152,34 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl viewModel.loadCoupons(reset, active, discountType, null); } + /** + * Navigates to the coupon detail screen. + */ private void openDetail(long id) { Bundle args = new Bundle(); args.putLong("couponId", id); Navigation.findNavController(requireView()).navigate(R.id.couponDetailFragment, args); } + /** + * Handles coupon item clicks by opening details. + */ @Override public void onCouponClick(CouponDTO coupon) { openDetail(coupon.getCouponId()); } + /** + * Shows or hides the bulk delete button based on selection count. + */ @Override public void onSelectionChanged(int count) { binding.btnBulkDeleteCoupons.setVisibility(count > 0 ? View.VISIBLE : View.GONE); } + /** + * Displays a confirmation dialog for deleting multiple coupons. + */ private void confirmBulkDelete() { new AlertDialog.Builder(requireContext()) .setTitle("Confirm Bulk Delete") diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CustomerFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CustomerFragment.java index 5afe4d1f..87155463 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CustomerFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/CustomerFragment.java @@ -22,6 +22,9 @@ import java.util.*; import javax.inject.Inject; import javax.inject.Named; +/** + * Fragment for displaying and managing a list of customers. + */ @AndroidEntryPoint public class CustomerFragment extends Fragment implements CustomerAdapter.OnCustomerClickListener { @@ -33,6 +36,9 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; + /** + * Inflates the layout, initializes ViewModel, and sets up UI components and filters. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -56,6 +62,9 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust return binding.getRoot(); } + /** + * Observes the ViewModel for customer list updates and loading status. + */ private void observeViewModel() { viewModel.getFilteredCustomers().observe(getViewLifecycleOwner(), list -> { customerList.clear(); @@ -68,6 +77,9 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust }); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new CustomerAdapter(customerList, this); adapter.setBaseUrl(baseUrl); @@ -92,15 +104,24 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust }); } + /** + * Configures the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Active", "Inactive"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCustomer, statuses, this::applyFilters); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchCustomer, this::applyFilters); } + /** + * Applies filters and triggers data reloading or filtering in ViewModel. + */ private void applyFilters() { String query = binding.etSearchCustomer.getText().toString().trim(); String status = binding.spinnerStatusCustomer.getSelectedItem() != null ? @@ -108,10 +129,16 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust viewModel.filter(query, status); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshCustomer.setOnRefreshListener(() -> viewModel.loadCustomers(true)); } + /** + * Navigates to the customer detail screen. + */ private void openDetail(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -129,11 +156,17 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust NavHostFragment.findNavController(this).navigate(R.id.nav_customer_detail, args); } + /** + * Handles customer item clicks by opening details. + */ @Override public void onCustomerClick(int position) { openDetail(position); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java index 4e2c2337..c01fb710 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java @@ -32,6 +32,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing inventory items. + */ @AndroidEntryPoint public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener { @@ -43,12 +46,18 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn @Inject TokenManager tokenManager; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class); } + /** + * Inflates the layout and sets up UI components, filters, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -71,6 +80,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn return binding.getRoot(); } + /** + * Observes the ViewModel for inventory list updates, store list, and loading status. + */ private void observeViewModel() { viewModel.getInventory().observe(getViewLifecycleOwner(), list -> { inventoryList.clear(); @@ -88,6 +100,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn }); } + /** + * Configures the bulk delete handler for multiple inventory item deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -101,18 +116,27 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn ); } + /** + * Reloads store data if necessary when the fragment resumes. + */ @Override public void onResume() { super.onResume(); if (!isStaff()) viewModel.loadStores(); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Sets up the filter visibility toggle, considering user roles. + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory); @@ -122,18 +146,30 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn } } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true)); } + /** + * Configures the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true)); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new InventoryAdapter(inventoryList, this); binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext())); @@ -156,10 +192,16 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn }); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true)); } + /** + * Loads inventory data based on current search query and store filter. + */ private void loadInventory(boolean reset) { String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : ""; if (query.isEmpty()) query = null; @@ -178,6 +220,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn viewModel.loadInventory(reset, query, storeId); } + /** + * Navigates to the inventory detail screen. + */ private void openDetail(InventoryDTO inv) { Bundle args = new Bundle(); if (inv != null) { @@ -186,6 +231,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args); } + /** + * Handles inventory item clicks by opening details. + */ @Override public void onInventoryClick(int position) { if (position >= 0 && position < inventoryList.size()) { @@ -193,6 +241,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn } } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int selectedCount) { if (bulkDeleteHandler != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index 96225e6a..c7139548 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -33,6 +33,9 @@ import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of pets. + */ @AndroidEntryPoint public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener { private FragmentPetBinding binding; @@ -44,12 +47,18 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; + /** + * Initializes the view model. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PetListViewModel.class); } + /** + * Inflates the layout and initializes UI components and filters. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -72,6 +81,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen return binding.getRoot(); } + /** + * Observes LiveData from the ViewModel to update the list and filter options. + */ private void observeViewModel() { viewModel.getPets().observe(getViewLifecycleOwner(), list -> { petList.clear(); @@ -94,6 +106,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen }); } + /** + * Configures the handler for bulk deletion of pets. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -107,6 +122,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen ); } + /** + * Refreshes pet data and filters when the fragment is resumed. + */ @Override public void onResume() { super.onResume(); @@ -115,6 +133,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen if (!isStaff()) viewModel.loadStores(); } + /** + * Sets up the visibility of filters based on the user's role. + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet, @@ -126,32 +147,53 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen } } + /** + * Checks if the current user has the 'STAFF' role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Attaches search functionality to the search input field. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchPet, () -> loadPetData(true)); } + /** + * Initializes the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Available", "Adopted", "Owned", "Pending"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadPetData(true)); } + /** + * Initializes the species filter spinner. + */ private void setupSpeciesFilter() { String[] initial = {"All Species"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, () -> loadPetData(true)); } + /** + * Initializes the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadPetData(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshPet.setOnRefreshListener(() -> loadPetData(true)); } + /** + * Triggers loading of pet data from the backend with current filters. + */ private void loadPetData(boolean reset) { String query = binding.etSearchPet.getText().toString().trim(); String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses"; @@ -171,6 +213,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen viewModel.loadPets(reset, query, status, species, storeId); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new PetAdapter(petList, this); adapter.setBaseUrl(baseUrl); @@ -195,6 +240,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen }); } + /** + * Navigates to the profile view of a specific pet. + */ private void openPetProfile(int position) { Bundle args = new Bundle(); PetDTO pet = petList.get(position); @@ -202,15 +250,24 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args); } + /** + * Navigates to the screen for adding a new pet. + */ private void openPetDetails() { NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail); } + /** + * Handles clicks on individual pets in the list. + */ @Override public void onPetClick(int position) { openPetProfile(position); } + /** + * Notifies the bulk delete handler when item selection changes. + */ @Override public void onSelectionChanged(int selectedCount) { if (bulkDeleteHandler != null) { @@ -218,6 +275,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen } } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java index 0820aca6..34d30e6a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java @@ -31,6 +31,9 @@ import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of products. + */ @AndroidEntryPoint public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener { @@ -41,12 +44,18 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc @Inject @Named("baseUrl") String baseUrl; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ProductListViewModel.class); } + /** + * Inflates the layout and sets up UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -66,6 +75,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc return binding.getRoot(); } + /** + * Observes the ViewModel for product list, categories, and loading status. + */ private void observeViewModel() { viewModel.getProducts().observe(getViewLifecycleOwner(), list -> { productList.clear(); @@ -83,6 +95,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc }); } + /** + * Reloads product data and categories when the fragment resumes. + */ @Override public void onResume() { super.onResume(); @@ -90,23 +105,38 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc viewModel.loadCategories(); } + /** + * Sets up the filter visibility toggle. + */ private void setupFilterToggle() { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchProduct, binding.spinnerCategory); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchProduct, () -> loadProductData(true)); } + /** + * Configures the category filter spinner. + */ private void setupCategoryFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, () -> loadProductData(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshProduct.setOnRefreshListener(() -> loadProductData(true)); } + /** + * Loads product data based on current filters and search query. + */ private void loadProductData(boolean reset) { String query = binding.etSearchProduct.getText().toString().trim(); if (query.isEmpty()) query = null; @@ -120,6 +150,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc viewModel.loadProducts(reset, query, categoryId); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new ProductAdapter(productList, this); adapter.setBaseUrl(baseUrl); @@ -143,6 +176,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc }); } + /** + * Navigates to the product detail screen. + */ private void openProductDetails(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -152,11 +188,17 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args); } + /** + * Handles product item clicks by opening details. + */ @Override public void onProductClick(int position) { openProductDetails(position); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java index c4751ae3..d3b63523 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java @@ -29,6 +29,9 @@ import java.util.List; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing the relationships between products and suppliers. + */ @AndroidEntryPoint public class ProductSupplierFragment extends Fragment implements ProductSupplierAdapter.OnProductSupplierClickListener { @@ -40,12 +43,18 @@ public class ProductSupplierFragment extends Fragment private ProductSupplierListViewModel viewModel; private BulkDeleteHandler bulkDeleteHandler; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class); } + /** + * Inflates the layout and sets up UI components, filters, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -67,6 +76,9 @@ public class ProductSupplierFragment extends Fragment return binding.getRoot(); } + /** + * Observes the ViewModel for product-supplier list, products, suppliers, and loading status. + */ private void observeViewModel() { viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> { psList.clear(); @@ -89,6 +101,9 @@ public class ProductSupplierFragment extends Fragment }); } + /** + * Configures the bulk delete handler for multiple product-supplier relationship deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -102,6 +117,9 @@ public class ProductSupplierFragment extends Fragment ); } + /** + * Reloads data and filter options when the fragment resumes. + */ @Override public void onResume() { super.onResume(); @@ -109,17 +127,26 @@ public class ProductSupplierFragment extends Fragment viewModel.loadFilterData(); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Sets up the filter visibility toggle. + */ private void setupFilterToggle() { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS, binding.spinnerProduct, binding.spinnerSupplier); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new ProductSupplierAdapter(psList, this); binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext())); @@ -142,22 +169,37 @@ public class ProductSupplierFragment extends Fragment }); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchPS, () -> loadData(true)); } + /** + * Configures the product filter spinner. + */ private void setupProductFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, () -> loadData(true)); } + /** + * Configures the supplier filter spinner. + */ private void setupSupplierFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, () -> loadData(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshPS.setOnRefreshListener(() -> loadData(true)); } + /** + * Loads product-supplier data based on current search query and filters. + */ private void loadData(boolean reset) { String query = binding.etSearchPS.getText().toString().trim(); if (query.isEmpty()) query = null; @@ -177,6 +219,9 @@ public class ProductSupplierFragment extends Fragment viewModel.loadProductSuppliers(reset, query, productId, supplierId); } + /** + * Navigates to the product-supplier detail screen. + */ private void openDetail(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -187,9 +232,15 @@ public class ProductSupplierFragment extends Fragment NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args); } + /** + * Handles product-supplier item clicks by opening details. + */ @Override public void onProductSupplierClick(int position) { openDetail(position); } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int count) { if (bulkDeleteHandler != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java index f4e4e230..ea4a2bbd 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java @@ -30,6 +30,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of purchase orders. + */ @AndroidEntryPoint public class PurchaseOrderFragment extends Fragment implements PurchaseOrderAdapter.OnPurchaseOrderClickListener { @@ -41,12 +44,18 @@ public class PurchaseOrderFragment extends Fragment @Inject TokenManager tokenManager; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class); } + /** + * Inflates the layout and sets up UI components, filters, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -64,6 +73,9 @@ public class PurchaseOrderFragment extends Fragment return binding.getRoot(); } + /** + * Observes the ViewModel for purchase order list, stores, and loading status. + */ private void observeViewModel() { viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> { poList.clear(); @@ -81,6 +93,9 @@ public class PurchaseOrderFragment extends Fragment }); } + /** + * Reloads data and stores if necessary when the fragment resumes. + */ @Override public void onResume() { super.onResume(); @@ -88,6 +103,9 @@ public class PurchaseOrderFragment extends Fragment if (!isStaff()) viewModel.loadStores(); } + /** + * Sets up the filter visibility toggle + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO); @@ -97,18 +115,30 @@ public class PurchaseOrderFragment extends Fragment } } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchPO, () -> loadData(true)); } + /** + * Configures the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadData(true)); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new PurchaseOrderAdapter(poList, this); binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext())); @@ -131,10 +161,16 @@ public class PurchaseOrderFragment extends Fragment }); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshPO.setOnRefreshListener(() -> loadData(true)); } + /** + * Loads purchase order data based on current search query and store filter. + */ private void loadData(boolean reset) { String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : ""; if (query.isEmpty()) query = null; @@ -153,6 +189,9 @@ public class PurchaseOrderFragment extends Fragment viewModel.loadPurchaseOrders(reset, query, storeId); } + /** + * Navigates to the purchase order detail screen. + */ private void openDetail(int position) { Bundle args = new Bundle(); PurchaseOrderDTO po = poList.get(position); @@ -160,11 +199,17 @@ public class PurchaseOrderFragment extends Fragment NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args); } + /** + * Handles purchase order item clicks by opening details. + */ @Override public void onPurchaseOrderClick(int position) { openDetail(position); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java index f6d0c388..890f8c68 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java @@ -31,6 +31,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of sales. + */ @AndroidEntryPoint public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener { @@ -41,6 +44,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis @Inject TokenManager tokenManager; + /** + * Inflates the layout for this fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -48,6 +54,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis return binding.getRoot(); } + /** + * Initializes UI components and observers after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -79,6 +88,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis NavHostFragment.findNavController(this).navigate(R.id.nav_refund)); } + /** + * Observes LiveData from the ViewModel to update the list and filter options. + */ private void observeViewModel() { viewModel.getSales().observe(getViewLifecycleOwner(), list -> { saleList.clear(); @@ -101,6 +113,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis }); } + /** + * Refreshes lookup data when the fragment is resumed. + */ @Override public void onResume() { super.onResume(); @@ -108,6 +123,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis viewModel.loadCustomers(); } + /** + * Sets up the visibility of filters. + */ private void setupFilterToggle() { if (isStaff()) { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale, @@ -119,32 +137,53 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis } } + /** + * Checks if the current user has the 'STAFF' role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Checks if the current user has the 'ADMIN' role. + */ private boolean isAdmin() { return "ADMIN".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Initializes the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true)); } + /** + * Initializes the payment method filter spinner. + */ private void setupPaymentMethodFilter() { String[] paymentMethods = {"All Payments", "Cash", "Card"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true)); } + /** + * Initializes the refund status filter spinner. + */ private void setupRefundStatusFilter() { String[] refundStatuses = {"All Status", "Sale", "Refund"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true)); } + /** + * Initializes the customer filter spinner. + */ private void setupCustomerFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerCustomer, () -> loadSales(true)); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new SaleAdapter(saleList, this); binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext())); @@ -168,14 +207,23 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis }); } + /** + * Attaches search functionality to the search input field. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchSale, () -> loadSales(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshSale.setOnRefreshListener(() -> loadSales(true)); } + /** + * Triggers loading of sale data from the backend with current filters. + */ private void loadSales(boolean reset) { String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : ""; if (query.isEmpty()) query = null; @@ -210,6 +258,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund, customerId); } + /** + * Handles clicks on individual sales in the list. + */ @Override public void onSaleClick(int position) { if (position < 0 || position >= saleList.size()) return; @@ -225,6 +276,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java index 1aaf625d..d5a73312 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java @@ -39,12 +39,18 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic private ServiceListViewModel viewModel; private BulkDeleteHandler bulkDeleteHandler; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class); } + /** + * Inflates the layout and sets up UI components and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -67,6 +73,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic return binding.getRoot(); } + /** + * Observes the ViewModel for service list updates and loading status. + */ private void observeViewModel() { viewModel.getServices().observe(getViewLifecycleOwner(), list -> { serviceList.clear(); @@ -79,6 +88,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic }); } + /** + * Configures the bulk delete handler for multiple service deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -92,20 +104,32 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic ); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Sets up the filter visibility toggle. + */ private void setupFilterToggle() { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true)); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new ServiceAdapter(serviceList, this); binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext())); @@ -128,16 +152,25 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic }); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true)); } + /** + * Loads service data based on current filters and search query. + */ private void loadServices(boolean reset) { String query = binding.etSearchService.getText().toString().trim(); if (query.isEmpty()) query = null; viewModel.loadServices(reset, query); } + /** + * Navigates to the service detail screen. + */ private void openDetail(ServiceDTO service) { Bundle args = new Bundle(); if (service != null) { @@ -146,6 +179,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args); } + /** + * Handles service item clicks by opening details. + */ @Override public void onServiceClick(int position) { if (position >= 0 && position < serviceList.size()) { @@ -153,6 +189,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic } } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int count) { if (bulkDeleteHandler != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/StaffFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/StaffFragment.java index e1e92ca7..81efdd4a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/StaffFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/StaffFragment.java @@ -24,6 +24,9 @@ import java.util.*; import javax.inject.Inject; import javax.inject.Named; +/** + * Fragment for displaying and managing a list of staff members. + */ @AndroidEntryPoint public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener { @@ -35,6 +38,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; + /** + * Inflates the layout and initializes UI components, filters, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -60,6 +66,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye return binding.getRoot(); } + /** + * Observes LiveData from the ViewModel to update the list and filter options. + */ private void observeViewModel() { viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> { staffList.clear(); @@ -77,6 +86,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye }); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new EmployeeAdapter(staffList, this); adapter.setBaseUrl(baseUrl); @@ -101,19 +113,31 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye }); } + /** + * Initializes the status filter spinner. + */ private void setupStatusFilter() { String[] statuses = {"All Statuses", "Active", "Inactive"}; SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusStaff, statuses, this::applyFilters); } + /** + * Initializes the store filter spinner. + */ private void setupStoreFilter() { SpinnerUtils.setupFilterSpinner(binding.spinnerStoreStaff, this::applyFilters); } + /** + * Attaches search functionality to the search input field. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchStaff, this::applyFilters); } + /** + * Applies the selected filters and triggers a data reload from the view model. + */ private void applyFilters() { String query = binding.etSearchStaff.getText().toString().trim(); String status = binding.spinnerStatusStaff.getSelectedItem() != null ? @@ -128,10 +152,16 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye viewModel.filter(query, storeId, status); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshStaff.setOnRefreshListener(() -> viewModel.loadStaff(true)); } + /** + * Navigates to the staff detail screen for adding or editing an employee. + */ private void openDetail(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -149,11 +179,17 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye NavHostFragment.findNavController(this).navigate(R.id.nav_staff_detail, args); } + /** + * Handles clicks on individual staff members in the list. + */ @Override public void onEmployeeClick(int position) { openDetail(position); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java index 163ac217..ca10c7d0 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java @@ -27,6 +27,9 @@ import java.util.List; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and managing a list of suppliers. + */ @AndroidEntryPoint public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener { @@ -36,12 +39,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp private SupplierListViewModel viewModel; private BulkDeleteHandler bulkDeleteHandler; + /** + * Initializes the ViewModel. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class); } + /** + * Inflates the layout and sets up UI components, bulk delete, and observers. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -63,6 +72,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp return binding.getRoot(); } + /** + * Observes the ViewModel for supplier list updates and loading status. + */ private void observeViewModel() { viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> { supplierList.clear(); @@ -75,6 +87,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp }); } + /** + * Configures the bulk delete handler for multiple supplier deletion. + */ private void setupBulkDelete() { bulkDeleteHandler = new BulkDeleteHandler( this, @@ -88,24 +103,39 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp ); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Sets up the filter visibility toggle. + */ private void setupFilterToggle() { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier); } + /** + * Sets up the search input listener. + */ private void setupSearch() { UIUtils.attachSearch(binding.etSearchSupplier, () -> loadSupplierData(true)); } + /** + * Configures the swipe-to-refresh layout. + */ private void setupSwipeRefresh() { binding.swipeRefreshSupplier.setOnRefreshListener(() -> loadSupplierData(true)); } + /** + * Navigates to the supplier detail screen. + */ private void openSupplierDetails(int position) { Bundle args = new Bundle(); if (position != -1) { @@ -115,11 +145,17 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args); } + /** + * Handles supplier item clicks by opening details. + */ @Override public void onSupplierClick(int position) { openSupplierDetails(position); } + /** + * Forwards selection changes to the bulk delete handler. + */ @Override public void onSelectionChanged(int count) { if (bulkDeleteHandler != null) { @@ -127,12 +163,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp } } + /** + * Loads supplier data based on current search query. + */ private void loadSupplierData(boolean reset) { String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : ""; if (query.isEmpty()) query = null; viewModel.loadSuppliers(reset, query); } + /** + * Configures the RecyclerView and its scroll listener for pagination. + */ private void setupRecyclerView() { adapter = new SupplierAdapter(supplierList, this); binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext())); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java index 1cd47561..64ec51d8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java @@ -39,12 +39,18 @@ public class AdoptionDetailFragment extends Fragment { @Inject TokenManager tokenManager; + /** + * Initializes the fragment and its ViewModel. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class); } + /** + * Inflates the layout for the fragment + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -52,6 +58,9 @@ public class AdoptionDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up UI components, observers, and loads initial data after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -68,6 +77,9 @@ public class AdoptionDetailFragment extends Fragment { binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete()); } + /** + * Sets up observers for ViewModel to update the UI dynamically. + */ private void observeViewModel() { viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); @@ -113,18 +125,27 @@ public class AdoptionDetailFragment extends Fragment { }); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Initializes the spinners with data and selection listeners. + */ private void setupSpinners() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{}); @@ -141,11 +162,17 @@ public class AdoptionDetailFragment extends Fragment { SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange()); } + /** + * Configures the date picker for the adoption date field. + */ private void setupDatePicker() { binding.etAdoptionDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange)); } + /** + * Notifies the ViewModel when the date or status changes to update available options. + */ private void notifyDateStatusChange() { if (isUpdatingUI) return; String date = binding.etAdoptionDate.getText().toString(); @@ -154,6 +181,9 @@ public class AdoptionDetailFragment extends Fragment { viewModel.onDateChanged(date, status); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("adoptionId")) { @@ -164,6 +194,9 @@ public class AdoptionDetailFragment extends Fragment { viewModel.setAdoptionId(-1); } + /** + * Load the adoption data from the backend. + */ private void loadAdoptionData() { viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -174,6 +207,10 @@ public class AdoptionDetailFragment extends Fragment { }); } + /** + * Applies the current ViewState to the UI elements. + * This handles enabling/disabling views and setting text/selections based on state. + */ private void applyViewState(AdoptionDetailViewModel.ViewState state) { isUpdatingUI = true; @@ -224,10 +261,16 @@ public class AdoptionDetailFragment extends Fragment { isUpdatingUI = false; } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Validates input and saves the adoption record. + */ private void saveAdoption() { if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return; if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return; @@ -275,6 +318,9 @@ public class AdoptionDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the adoption record. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () -> viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> { @@ -289,6 +335,9 @@ public class AdoptionDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java index baec0c28..a426cf8e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java @@ -47,18 +47,27 @@ public class AppointmentDetailFragment extends Fragment { @Inject TokenManager tokenManager; + /** + * Initializes the fragment and its ViewModel. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class); } + /** + * Inflates the layout for the fragment + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false); return binding.getRoot(); } + /** + * Sets up UI components, observers, and loads initial data after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -73,12 +82,18 @@ public class AppointmentDetailFragment extends Fragment { binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete()); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Initializes the spinners with data and selection listeners. + */ private void setupSpinners() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{}); @@ -108,11 +123,17 @@ public class AppointmentDetailFragment extends Fragment { SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange()); } + /** + * Configures the date picker for the appointment date field. + */ private void setupDatePicker() { binding.etAppointmentDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange)); } + /** + * Sets up observers for ViewModel to update the UI dynamically. + */ private void observeViewModel() { viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); @@ -147,12 +168,19 @@ public class AppointmentDetailFragment extends Fragment { }); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Applies the current ViewState to the UI elements. + * This handles enabling/disabling views and setting text/selections based on state. + */ private void applyViewState(AppointmentDetailViewModel.ViewState state) { isUpdatingUI = true; @@ -201,10 +229,16 @@ public class AppointmentDetailFragment extends Fragment { isUpdatingUI = false; } + /** + * Checks if the currently logged-in user has the STAFF role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Notifies the ViewModel when the date, time, or status changes to update available options. + */ private void notifyDateTimeStatusChange() { if (isUpdatingUI) return; @@ -215,6 +249,9 @@ public class AppointmentDetailFragment extends Fragment { viewModel.onDateOrTimeChanged(date, time, status); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("appointmentId")) { @@ -225,6 +262,9 @@ public class AppointmentDetailFragment extends Fragment { } } + /** + * Load the appointment data from the backend. + */ private void loadAppointmentData() { viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -244,6 +284,9 @@ public class AppointmentDetailFragment extends Fragment { }); } + /** + * Validates input and saves the appointment record. + */ private void saveAppointment() { if (!validateRequiredFields()) return; @@ -275,6 +318,9 @@ public class AppointmentDetailFragment extends Fragment { }); } + /** + * Validates that all required fields are selected or filled. + */ private boolean validateRequiredFields() { if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false; if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false; @@ -284,15 +330,24 @@ public class AppointmentDetailFragment extends Fragment { return true; } + /** + * Formats the selected hour and minute from spinners into a time string. + */ private String buildTimeString() { return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]); } + /** + * Handles errors that occur during the save process. + */ private void handleSaveError(String errorMessage) { if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog(); else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show(); } + /** + * Displays a dialog when the selected time slot is not available. + */ private void showNoAvailabilityDialog() { new androidx.appcompat.app.AlertDialog.Builder(requireContext()) .setTitle("No Availability") @@ -301,6 +356,9 @@ public class AppointmentDetailFragment extends Fragment { .setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show(); } + /** + * Displays a confirmation dialog before deleting the appointment record. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () -> viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> { @@ -310,10 +368,16 @@ public class AppointmentDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Parses a time string and sets the hour and minute spinners accordingly. + */ private void parseAndSetTimeSpinners(String time) { int[] parsedTime = DateTimeUtils.parseTimeString(time); if (parsedTime == null) return; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CouponDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CouponDetailFragment.java index 0d243a69..47145bcf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CouponDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CouponDetailFragment.java @@ -29,12 +29,18 @@ import java.util.Locale; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and editing coupon details. + */ @AndroidEntryPoint public class CouponDetailFragment extends Fragment { private FragmentCouponDetailBinding binding; private CouponDetailViewModel viewModel; private long couponId = -1; + /** + * Inflates the layout, initializes ViewModel, and sets up UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -74,10 +80,16 @@ public class CouponDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Initializes the discount type spinner with options. + */ private void setupDiscountTypeSpinner() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerDiscountTypeDetail, new String[]{"FIXED", "PERCENT"}); } + /** + * Configures a date picker for an EditText field. + */ private void setupDatePicker(android.widget.EditText editText, android.widget.EditText dependOn, Runnable onDateSet) { editText.setFocusable(false); editText.setClickable(true); @@ -91,6 +103,9 @@ public class CouponDetailFragment extends Fragment { }); } + /** + * Loads coupon details from the backend and populates the UI. + */ private void loadCouponDetails() { binding.tvCouponId.setText(DateTimeUtils.formatId(couponId)); binding.tvCouponId.setVisibility(View.VISIBLE); @@ -111,6 +126,9 @@ public class CouponDetailFragment extends Fragment { }); } + /** + * Validates input and saves the coupon record. + */ private void saveCoupon() { if (!InputValidator.isNotEmpty(binding.etCouponCodeDetail, "Coupon Code")) return; if (!InputValidator.isGreaterThanZero(binding.etDiscountValueDetail, "Discount Value")) return; @@ -156,6 +174,9 @@ public class CouponDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the coupon. + */ private void confirmDelete() { new AlertDialog.Builder(requireContext()) .setTitle("Delete Coupon") diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CustomerDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CustomerDetailFragment.java index 455450cc..017c80e2 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CustomerDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/CustomerDetailFragment.java @@ -21,6 +21,9 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and editing customer details. + */ @AndroidEntryPoint public class CustomerDetailFragment extends Fragment { @@ -31,6 +34,9 @@ public class CustomerDetailFragment extends Fragment { private final String[] STATUSES = {"Active", "Inactive"}; + /** + * Inflates the layout, initializes ViewModel, and sets up UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -49,10 +55,16 @@ public class CustomerDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Initializes the spinners with data. + */ private void setupSpinners() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerCustomerStatus, STATUSES); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.getBoolean("isEditing", false)) { @@ -86,6 +98,9 @@ public class CustomerDetailFragment extends Fragment { } } + /** + * Loads customer data from the backend. + */ private void loadCustomerData(long id) { viewModel.loadCustomer(id).observe(getViewLifecycleOwner(), resource -> { if (resource != null) { @@ -105,12 +120,18 @@ public class CustomerDetailFragment extends Fragment { }); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Validates input and saves the customer record. + */ private void save() { if (!InputValidator.isNotEmpty(binding.etCustomerUsername, "Username")) return; @@ -160,6 +181,9 @@ public class CustomerDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the customer. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Customer", () -> viewModel.deleteCustomer().observe(getViewLifecycleOwner(), resource -> { @@ -175,10 +199,16 @@ public class CustomerDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java index 75f1a6b3..f0e37927 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java @@ -37,12 +37,18 @@ public class InventoryDetailFragment extends Fragment { private long preselectedStoreId = -1; private long preselectedProductId = -1; + /** + * Initializes the fragment and its ViewModel. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class); } + /** + * Inflates the layout for the fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -50,6 +56,9 @@ public class InventoryDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up UI components, observers, and loads initial data after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -63,23 +72,35 @@ public class InventoryDetailFragment extends Fragment { binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete()); } + /** + * Sets up observers for ViewModel to update the UI dynamically. + */ private void observeViewModel() { viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner()); viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner()); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Loads initial data for the store and product spinners. + */ private void loadSpinnersData() { viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -97,18 +118,27 @@ public class InventoryDetailFragment extends Fragment { }); } + /** + * Refreshes the store spinner with the current data. + */ private void refreshStoreSpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(), DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId); } + /** + * Refreshes the product spinner with the current data. + */ private void refreshProductSpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(), DropdownDTO::getLabel, "-- Select Product --", preselectedProductId, DropdownDTO::getId); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle args = getArguments(); if (args != null && args.containsKey("inventoryId")) { @@ -131,6 +161,9 @@ public class InventoryDetailFragment extends Fragment { } } + /** + * Loads the inventory data from the backend. + */ private void loadInventoryData() { viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -149,6 +182,9 @@ public class InventoryDetailFragment extends Fragment { }); } + /** + * Validates input and saves the inventory record. + */ private void saveInventory() { if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) return; if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryProduct, "Product")) return; @@ -176,10 +212,16 @@ public class InventoryDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the inventory record. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory); } + /** + * Calls the ViewModel to delete the inventory record. + */ private void deleteInventory() { setButtonsEnabled(false); viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> { @@ -197,10 +239,16 @@ public class InventoryDetailFragment extends Fragment { }); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Enables or disables the action buttons. + */ private void setButtonsEnabled(boolean enabled) { UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java index 5d89e305..7119a447 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java @@ -58,6 +58,9 @@ public class ProductDetailFragment extends Fragment { @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; + /** + * Initializes the fragment, its ViewModel, and the ImagePickerHelper. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -88,6 +91,9 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Inflates the layout for the fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -95,6 +101,9 @@ public class ProductDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up UI components, observers, and handles arguments after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -108,6 +117,9 @@ public class ProductDetailFragment extends Fragment { binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage)); } + /** + * Sets up observers for the ViewModel and loads product categories. + */ private void observeViewModel() { viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner()); @@ -120,24 +132,36 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Populates the category spinner with available categories. + */ private void updateCategorySpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(), DropdownDTO::getLabel, "-- Select Category --", preselectedCategoryId, DropdownDTO::getId); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("prodId")) { @@ -158,6 +182,9 @@ public class ProductDetailFragment extends Fragment { } } + /** + * Loads the product details from the backend. + */ private void loadProductData() { viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -175,6 +202,9 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Loads the product image from the server. + */ private void loadProductImage() { String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId()); String token = tokenManager.getToken(); @@ -192,6 +222,9 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Performs image-related actions (removal or upload) after a successful product save. + */ private void performPendingImageActions(String successMsg) { if (isImageRemoved) { viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> { @@ -214,6 +247,9 @@ public class ProductDetailFragment extends Fragment { } } + /** + * Uploads the selected product image to the server. + */ private void uploadProductImageAndNavigate(Uri uri, String successMsg) { File file = FileUtils.getFileFromUri(requireContext(), uri); if (file == null) { @@ -239,6 +275,9 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Validates input and saves the product record. + */ private void saveProduct() { if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return; if (!InputValidator.isSpinnerSelected(binding.spinnerProductCategory, "Category")) return; @@ -267,6 +306,9 @@ public class ProductDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the product. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () -> viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> { @@ -280,6 +322,9 @@ public class ProductDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java index 6abb918c..e0ba9409 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java @@ -35,12 +35,18 @@ public class ProductSupplierDetailFragment extends Fragment { private long preselectedProductId = -1; private long preselectedSupplierId = -1; + /** + * Initializes the view model. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ProductSupplierDetailViewModel.class); } + /** + * Inflates the layout for this fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -48,6 +54,9 @@ public class ProductSupplierDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Initializes the UI components and observers after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -60,23 +69,35 @@ public class ProductSupplierDetailFragment extends Fragment { binding.btnDeletePS.setOnClickListener(v -> confirmDelete()); } + /** + * Observes LiveData from the ViewModel to update the UI. + */ private void observeViewModel() { viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner()); viewModel.getSupplierList().observe(getViewLifecycleOwner(), list -> refreshSupplierSpinner()); } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Loads lookup data for products and suppliers. + */ private void loadSpinnersData() { viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -94,18 +115,27 @@ public class ProductSupplierDetailFragment extends Fragment { }); } + /** + * Refreshes the product spinner with data from the view model. + */ private void refreshProductSpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, viewModel.getProductList().getValue(), ProductDTO::getProdName, "-- Select Product --", preselectedProductId, ProductDTO::getProdId); } + /** + * Refreshes the supplier spinner with data from the view model. + */ private void refreshSupplierSpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, viewModel.getSupplierList().getValue(), SupplierDTO::getSupCompany, "-- Select Supplier --", preselectedSupplierId, SupplierDTO::getSupId); } + /** + * Handles fragment arguments to determine mode (new vs edit). + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) { @@ -132,6 +162,9 @@ public class ProductSupplierDetailFragment extends Fragment { } } + /** + * Validates and saves the product-supplier relationship. + */ private void save() { if (!InputValidator.isSpinnerSelected(binding.spinnerPSProduct, "Product")) return; if (!InputValidator.isSpinnerSelected(binding.spinnerPSSupplier, "Supplier")) return; @@ -155,6 +188,9 @@ public class ProductSupplierDetailFragment extends Fragment { }); } + /** + * Shows a confirmation dialog before deleting the relationship. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier Relationship", () -> viewModel.deleteProductSupplier().observe(getViewLifecycleOwner(), resource -> { @@ -169,6 +205,9 @@ public class ProductSupplierDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous fragment. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java index d1bbd31c..aca42375 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java @@ -27,6 +27,9 @@ public class PurchaseOrderDetailFragment extends Fragment { private PurchaseOrderDetailViewModel viewModel; private long purchaseOrderId; + /** + * Initializes the view model. + */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -57,6 +60,9 @@ public class PurchaseOrderDetailFragment extends Fragment { }); } + /** + * Handles fragment arguments to retrieve the purchase order ID. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("purchaseOrderId")) { @@ -65,12 +71,18 @@ public class PurchaseOrderDetailFragment extends Fragment { } } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Loads the details of the specified purchase order. + */ private void loadPurchaseOrderData() { viewModel.loadPurchaseOrder(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -87,6 +99,9 @@ public class PurchaseOrderDetailFragment extends Fragment { }); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java index 0dbd985f..6165be7b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java @@ -21,6 +21,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; +/** + * Fragment for processing refunds for existing sales. + */ @AndroidEntryPoint public class RefundFragment extends Fragment { @@ -29,6 +32,9 @@ public class RefundFragment extends Fragment { private final String[] PAYMENT_METHODS = {"Cash", "Card"}; + /** + * Initializes the fragment's UI and view model. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -51,25 +57,37 @@ public class RefundFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up the payment method spinner. + */ private void setupSpinner() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerRefundPayment, PAYMENT_METHODS); } + /** + * Observes LiveData from the ViewModel to update the UI. + */ private void observeViewModel() { viewModel.getAvailableItems().observe(getViewLifecycleOwner(), items -> renderOriginalItems()); viewModel.getRefundCart().observe(getViewLifecycleOwner(), cart -> { renderRefundCart(); updateRefundTotal(); - renderOriginalItems(); // Re-render to reflect quantities in cart + renderOriginalItems(); }); } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Loads the list of all sales to allow looking up the sale to refund. + */ private void loadAllSales() { viewModel.loadAllSales().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -84,6 +102,9 @@ public class RefundFragment extends Fragment { }); } + /** + * Loads the details of a specific sale for refund processing. + */ private void loadSale() { String idStr = binding.etRefundSaleId.getText().toString().trim(); if (idStr.isEmpty()) { @@ -145,6 +166,9 @@ public class RefundFragment extends Fragment { binding.btnProcessRefund.setVisibility(View.VISIBLE); } + /** + * Renders the items from the original sale that are available for refund. + */ private void renderOriginalItems() { binding.llOriginalItems.removeAllViews(); List available = viewModel.getAvailableItems().getValue(); @@ -173,6 +197,9 @@ public class RefundFragment extends Fragment { } } + /** + * Renders the items selected for the refund. + */ private void renderRefundCart() { binding.llRefundItems.removeAllViews(); List cart = viewModel.getRefundCart().getValue(); @@ -200,6 +227,9 @@ public class RefundFragment extends Fragment { } } + /** + * Adds a header row to the layout representing a table. + */ private void addTableHeader(LinearLayout parent) { if (getContext() == null) return; LinearLayout header = new LinearLayout(getContext()); @@ -220,6 +250,9 @@ public class RefundFragment extends Fragment { parent.addView(header); } + /** + * Builds a UI row for an item in the original list or refund cart. + */ private LinearLayout buildItemRow(String name, int qty, BigDecimal unitPrice, boolean isAdd, Runnable action) { if (getContext() == null) return new LinearLayout(getContext()); @@ -267,6 +300,9 @@ public class RefundFragment extends Fragment { return row; } + /** + * Shows a dialog to select the quantity of an item to refund. + */ private void showQuantityDialog(RefundViewModel.RefundItem item, int available) { EditText input = new EditText(getContext()); input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); @@ -301,11 +337,17 @@ public class RefundFragment extends Fragment { .show(); } + /** + * Updates the total refund amount display. + */ private void updateRefundTotal() { BigDecimal total = viewModel.calculateRefundTotal(); binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP)); } + /** + * Validates and prepares the refund for processing. + */ private void processRefund() { if (viewModel.getCurrentSale() == null) { Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show(); @@ -325,6 +367,9 @@ public class RefundFragment extends Fragment { () -> submitRefund(payment)); } + /** + * Submits the refund request to the backend. + */ private void submitRefund(String payment) { viewModel.submitRefund(payment).observe(getViewLifecycleOwner(), resource -> { if (resource != null) { @@ -339,10 +384,16 @@ public class RefundFragment extends Fragment { }); } + /** + * Navigates back to the previous fragment. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java index 4963b272..b1ecb2b9 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java @@ -26,6 +26,9 @@ import dagger.hilt.android.AndroidEntryPoint; import java.math.BigDecimal; import java.util.*; +/** + * Fragment for viewing or creating sale details. + */ @AndroidEntryPoint public class SaleDetailFragment extends Fragment { @@ -36,6 +39,9 @@ public class SaleDetailFragment extends Fragment { private final String[] PAYMENT_METHODS = { "Cash", "Card"}; + /** + * Initializes the fragment's UI and view model. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -65,14 +71,23 @@ public class SaleDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Checks if the current user has the 'STAFF' role. + */ private boolean isStaff() { return "STAFF".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Checks if the current user has the 'ADMIN' role. + */ private boolean isAdmin() { return "ADMIN".equalsIgnoreCase(tokenManager.getRole()); } + /** + * Observes LiveData from the ViewModel to update the UI. + */ private void observeViewModel() { viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> { Long primaryStoreId = tokenManager.getPrimaryStoreId(); @@ -115,6 +130,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Handles fragment arguments to determine mode (new sale vs view existing). + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("saleId")) { @@ -159,12 +177,18 @@ public class SaleDetailFragment extends Fragment { } } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Loads lookup data for stores, customers, and products. + */ private void loadData() { viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -183,6 +207,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Loads the details of an existing sale. + */ private void loadSaleDetails() { viewModel.loadSaleDetails().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -241,6 +268,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Sets up the coupon application logic. + */ private void setupCoupon() { binding.btnApplyCoupon.setOnClickListener(v -> { String code = binding.etCouponCode.getText().toString().trim(); @@ -284,6 +314,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Updates the UI to reflect an applied coupon. + */ private void applyAppliedCouponUI(CouponDTO coupon) { String info; if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) { @@ -299,12 +332,18 @@ public class SaleDetailFragment extends Fragment { binding.etCouponCode.setEnabled(false); } + /** + * Displays an error message related to coupon validation. + */ private void showCouponError(String message) { binding.tvCouponInfo.setText(message); binding.tvCouponInfo.setTextColor(0xFFE53935); binding.tvCouponInfo.setVisibility(View.VISIBLE); } + /** + * Sets up the logic for adding items to the sale cart. + */ private void setupAddItem() { binding.btnAddItem.setOnClickListener(v -> { if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return; @@ -338,6 +377,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Renders the list of items currently in the cart. + */ private void renderCartItems() { binding.llSaleItems.removeAllViews(); List items = viewModel.getCartItems().getValue(); @@ -358,6 +400,9 @@ public class SaleDetailFragment extends Fragment { } } + /** + * Adds a row representing a sale item to the layout. + */ private void addItemRow(String name, int qty, BigDecimal price, Long prodId) { if (getContext() == null) return; LinearLayout row = new LinearLayout(getContext()); @@ -397,6 +442,9 @@ public class SaleDetailFragment extends Fragment { binding.llSaleItems.addView(row); } + /** + * Updates the subtotal, discounts, and total amount display. + */ private void updateTotal() { BigDecimal subtotal = viewModel.calculateSubtotal(); BigDecimal couponDiscount = viewModel.calculateCouponDiscount(); @@ -432,6 +480,9 @@ public class SaleDetailFragment extends Fragment { } } + /** + * Validates and saves the new sale. + */ private void saveSale() { if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return; @@ -468,6 +519,9 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Shows a confirmation dialog before proceeding to the refund screen. + */ private void showRefundDialog() { DialogUtils.showConfirmDialog(requireContext(), "Process Refund", "Are you sure you want to process a refund for this sale?", () -> { @@ -477,10 +531,16 @@ public class SaleDetailFragment extends Fragment { }); } + /** + * Navigates back to the previous fragment. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java index d7ee6e4c..0a0612c5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java @@ -35,12 +35,18 @@ public class ServiceDetailFragment extends Fragment { private FragmentServiceDetailBinding binding; private ServiceDetailViewModel viewModel; + /** + * Initializes the fragment and its ViewModel. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ServiceDetailViewModel.class); } + /** + * Inflates the layout for the fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -48,6 +54,9 @@ public class ServiceDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up UI components, observers, and handles arguments after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -60,22 +69,34 @@ public class ServiceDetailFragment extends Fragment { binding.btnDeleteService.setOnClickListener(v -> deleteService()); } + /** + * Sets up observers for ViewModel to update the UI dynamically. + */ private void observeViewModel() { viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Validates input and saves the service record. + */ private void saveService() { if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return; if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return; @@ -111,6 +132,9 @@ public class ServiceDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the service record. + */ private void deleteService() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () -> viewModel.deleteService().observe(getViewLifecycleOwner(), resource -> { @@ -126,10 +150,16 @@ public class ServiceDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { if (getArguments() != null && getArguments().containsKey("serviceId")) { viewModel.setServiceId(getArguments().getLong("serviceId")); @@ -140,6 +170,9 @@ public class ServiceDetailFragment extends Fragment { viewModel.setServiceId(-1); } + /** + * Loads the service details from the backend. + */ private void loadServiceData() { viewModel.loadService().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -150,6 +183,9 @@ public class ServiceDetailFragment extends Fragment { }); } + /** + * Applies the current ViewState to the UI elements. + */ private void applyViewState(ServiceDetailViewModel.ViewState state) { binding.tvMode.setText(state.modeTitle); binding.tvServiceId.setText(DateTimeUtils.formatId(viewModel.getServiceId())); @@ -169,6 +205,9 @@ public class ServiceDetailFragment extends Fragment { updateIfDifferent(binding.etServicePrice, state.servicePrice); } + /** + * Updates an EditText field only if the new value is different from the current one. + */ private void updateIfDifferent(EditText field, String value) { String current = field.getText() != null ? field.getText().toString() : ""; String next = value != null ? value : ""; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java index 9bb3435d..0f69f7b5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java @@ -22,6 +22,9 @@ import java.util.List; import dagger.hilt.android.AndroidEntryPoint; +/** + * Fragment for displaying and editing staff details. + */ @AndroidEntryPoint public class StaffDetailFragment extends Fragment { @@ -32,6 +35,9 @@ public class StaffDetailFragment extends Fragment { private long preselectedStoreId = -1; + /** + * Inflates the layout, initializes ViewModel, and sets up UI components. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -52,14 +58,23 @@ public class StaffDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Sets up observers for the ViewModel to refresh the store spinner. + */ private void observeViewModel() { viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner()); } + /** + * Initializes the status spinner with options. + */ private void setupSpinners() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES); } + /** + * Loads the list of stores from the backend. + */ private void loadStores() { viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -68,6 +83,9 @@ public class StaffDetailFragment extends Fragment { }); } + /** + * Populates the store spinner with available stores. + */ private void refreshStoreSpinner() { List list = viewModel.getStoreList().getValue(); if (list == null) return; @@ -76,6 +94,9 @@ public class StaffDetailFragment extends Fragment { preselectedStoreId, DropdownDTO::getId); } + /** + * Uses fragment arguments to determine if we are editing an existing record. + */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.getBoolean("isEditing", false)) { @@ -96,6 +117,9 @@ public class StaffDetailFragment extends Fragment { } } + /** + * Loads staff member details from the backend. + */ private void loadEmployeeData(long id) { viewModel.loadEmployee(id).observe(getViewLifecycleOwner(), resource -> { if (resource != null) { @@ -117,12 +141,18 @@ public class StaffDetailFragment extends Fragment { }); } + /** + * Shows or hides the loading bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Validates input and saves the staff record. + */ private void save() { if (!InputValidator.isNotEmpty(binding.etStaffUsername, "Username")) return; @@ -198,6 +228,9 @@ public class StaffDetailFragment extends Fragment { }); } + /** + * Displays a confirmation dialog before deleting the staff account. + */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Staff Account", () -> viewModel.deleteEmployee().observe(getViewLifecycleOwner(), resource -> { @@ -213,10 +246,16 @@ public class StaffDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous screen. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java index 5eb1f43b..513e627c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java @@ -35,12 +35,18 @@ public class SupplierDetailFragment extends Fragment { private FragmentSupplierDetailBinding binding; private SupplierDetailViewModel viewModel; + /** + * Initializes the view model. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(SupplierDetailViewModel.class); } + /** + * Inflates the layout for this fragment. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -48,6 +54,9 @@ public class SupplierDetailFragment extends Fragment { return binding.getRoot(); } + /** + * Initializes the UI components and observers after the view is created. + */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -61,22 +70,34 @@ public class SupplierDetailFragment extends Fragment { binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier()); } + /** + * Observes LiveData from the ViewModel to update the UI. + */ private void observeViewModel() { viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); } + /** + * Toggles the visibility of the progress bar. + */ private void setLoading(boolean loading) { if (binding != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding when the view is destroyed. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Validates and saves the supplier information. + */ private void saveSupplier() { if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return; if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return; @@ -115,6 +136,9 @@ public class SupplierDetailFragment extends Fragment { }); } + /** + * Deletes the current supplier after confirmation. + */ private void deleteSupplier() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () -> viewModel.deleteSupplier().observe(getViewLifecycleOwner(), resource -> { @@ -130,10 +154,16 @@ public class SupplierDetailFragment extends Fragment { })); } + /** + * Navigates back to the previous fragment. + */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } + /** + * Handles fragment arguments to determine mode (new vs edit). + */ private void handleArguments() { if (getArguments() != null && getArguments().containsKey("supId")) { viewModel.setSupId(getArguments().getLong("supId")); @@ -144,6 +174,9 @@ public class SupplierDetailFragment extends Fragment { viewModel.setSupId(-1); } + /** + * Loads the details of the specified supplier. + */ private void loadSupplierData() { viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -154,6 +187,9 @@ public class SupplierDetailFragment extends Fragment { }); } + /** + * Applies the current view state to the UI components. + */ private void applyViewState(SupplierDetailViewModel.ViewState state) { binding.tvMode.setText(state.modeTitle); binding.tvSupId.setText(DateTimeUtils.formatId(viewModel.getSupId())); @@ -175,6 +211,9 @@ public class SupplierDetailFragment extends Fragment { updateIfDifferent(binding.etSupPhone, state.supPhone); } + /** + * Updates an EditText field only if the new value is different from the current one. + */ private void updateIfDifferent(EditText field, String value) { String current = field.getText() != null ? field.getText().toString() : ""; String next = value != null ? value : ""; 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 496304a3..7cfa0910 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 @@ -36,6 +36,9 @@ import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; +/** + * Fragment for displaying and managing a pet's profile. + */ @AndroidEntryPoint public class PetProfileFragment extends Fragment { @@ -49,6 +52,9 @@ public class PetProfileFragment extends Fragment { private PetProfileViewModel viewModel; private ImagePickerHelper imagePickerHelper; + /** + * Initializes the ViewModel and image picker helper. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -67,6 +73,9 @@ public class PetProfileFragment extends Fragment { }); } + /** + * Inflates the layout, handles arguments, and sets up click listeners. + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -95,18 +104,27 @@ public class PetProfileFragment extends Fragment { return binding.getRoot(); } + /** + * Shows or hides the loading progress bar. + */ private void setLoading(boolean loading) { if (binding != null && binding.progressBar != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } + /** + * Cleans up the binding reference. + */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + /** + * Loads pet data from the ViewModel and updates the UI. + */ private void loadPetData() { viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; @@ -153,6 +171,9 @@ public class PetProfileFragment extends Fragment { }); } + /** + * Loads the pet's image using Glide with authentication. + */ private void loadPetImage(int petId) { String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId); String token = tokenManager.getToken(); @@ -170,6 +191,9 @@ public class PetProfileFragment extends Fragment { }); } + /** + * Uploads a new image for the pet. + */ private void uploadPetImage(Uri uri) { try { File file = FileUtils.getFileFromUri(requireContext(), uri); @@ -193,6 +217,9 @@ public class PetProfileFragment extends Fragment { } } + /** + * Deletes the pet's current image. + */ private void deletePetImage() { viewModel.deletePetImage(petId).observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Chat.java b/android/app/src/main/java/com/example/petstoremobile/models/Chat.java index a519093c..9d782b05 100644 --- a/android/app/src/main/java/com/example/petstoremobile/models/Chat.java +++ b/android/app/src/main/java/com/example/petstoremobile/models/Chat.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.models; +/** + * Model class representing a chat conversation. + */ public class Chat { private String chatId; private String customerName; diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Message.java b/android/app/src/main/java/com/example/petstoremobile/models/Message.java index e6547d23..2e0ff23e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/models/Message.java +++ b/android/app/src/main/java/com/example/petstoremobile/models/Message.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.models; +/** + * Model class representing a chat message. + */ public class Message { private Long id; private Long conversationId; diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Sale.java b/android/app/src/main/java/com/example/petstoremobile/models/Sale.java index ce305d58..c67126e1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/models/Sale.java +++ b/android/app/src/main/java/com/example/petstoremobile/models/Sale.java @@ -1,5 +1,8 @@ package com.example.petstoremobile.models; +/** + * Model class representing a sale transaction. + */ public class Sale { private int saleId; private String saleDate; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java index 6c8acddf..ac890c9a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java @@ -11,6 +11,9 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for retrieving activity logs from the ActivityLogApi. + */ @Singleton public class ActivityLogRepository extends BaseRepository { private final ActivityLogApi activityLogApi; @@ -21,6 +24,9 @@ public class ActivityLogRepository extends BaseRepository { this.activityLogApi = activityLogApi; } + /** + * Retrieves a list of activity logs with optional filtering. + */ public LiveData>> getActivityLogs(int limit, Long storeId, String role, String search, String startDate, String endDate) { return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search, startDate, endDate)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java index 8758357a..fd9e5e3c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing adoption data through the AdoptionApi. + */ @Singleton public class AdoptionRepository extends BaseRepository { private final AdoptionApi adoptionApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java index 6c2c4cf8..3f20ff3e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing service appointments. + */ @Singleton public class AppointmentRepository extends BaseRepository { private final AppointmentApi appointmentApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java index 6011bac8..3e8fa71a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java @@ -22,6 +22,9 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +/** + * Repository class for handling authentication-related operations. + */ @Singleton public class AuthRepository extends BaseRepository { private final AuthApi authApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/BaseRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/BaseRepository.java index cf98dfe8..f56067f4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/BaseRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/BaseRepository.java @@ -9,7 +9,7 @@ import com.example.petstoremobile.utils.RetrofitUtils; import retrofit2.Call; /** - * Base class for all repositories to provide common functionality for API calls. + * Base repository class providing common functionality for executing API calls and handling responses. */ public abstract class BaseRepository { protected final String TAG; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java index 8d11511b..f518615b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java @@ -10,6 +10,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing product and service categories. + */ @Singleton public class CategoryRepository extends BaseRepository { private final CategoryApi categoryApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java index ec8e32b6..4e5f3ada 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java @@ -23,7 +23,7 @@ import okhttp3.RequestBody; import okhttp3.ResponseBody; /** - * Repository for handling chat-related data operations. + * Repository class for managing chat messages and conversations. */ @Singleton public class ChatRepository extends BaseRepository { diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/CouponRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/CouponRepository.java index f97d1ca0..83f2289f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/CouponRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/CouponRepository.java @@ -12,6 +12,9 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing coupon data. + */ @Singleton public class CouponRepository extends BaseRepository { private final CouponApi couponApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/CustomerRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/CustomerRepository.java index 65c9a79d..6067b625 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/CustomerRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/CustomerRepository.java @@ -13,6 +13,9 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing customer information. + */ @Singleton public class CustomerRepository extends BaseRepository { private final CustomerApi customerApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/EmployeeRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/EmployeeRepository.java index b6ddcc9c..745936e6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/EmployeeRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/EmployeeRepository.java @@ -10,6 +10,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing employee data and roles. + */ @Singleton public class EmployeeRepository extends BaseRepository { private final EmployeeApi employeeApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java index 94526d25..83e8b9c1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing inventory and stock levels. + */ @Singleton public class InventoryRepository extends BaseRepository { private final InventoryApi inventoryApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java index 11bb9ff1..f035fa92 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java @@ -16,6 +16,9 @@ import javax.inject.Singleton; import okhttp3.MultipartBody; +/** + * Repository class for managing pet data through the PetApi. + */ @Singleton public class PetRepository extends BaseRepository { private final PetApi petApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java index 636e1430..1fca4df1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java @@ -15,6 +15,9 @@ import javax.inject.Singleton; import okhttp3.MultipartBody; +/** + * Repository class for managing product information and inventory levels. + */ @Singleton public class ProductRepository extends BaseRepository { private final ProductApi productApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java index 9b2f8df3..41d0393f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing relationships between products and their suppliers. + */ @Singleton public class ProductSupplierRepository extends BaseRepository { private final ProductSupplierApi api; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java index dd9bd637..70537ade 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java @@ -10,6 +10,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing purchase orders with suppliers. + */ @Singleton public class PurchaseOrderRepository extends BaseRepository { private final PurchaseOrderApi purchaseOrderApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/SaleRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/SaleRepository.java index 182a7f0e..4fb697a0 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/SaleRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/SaleRepository.java @@ -10,6 +10,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing sale transactions and refund data. + */ @Singleton public class SaleRepository extends BaseRepository { private final SaleApi saleApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java index bd5f3ebc..98e8b457 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing pet service offerings. + */ @Singleton public class ServiceRepository extends BaseRepository { private final ServiceApi serviceApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java index 0df93ab1..dca03260 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java @@ -13,6 +13,9 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for retrieving store-related information. + */ @Singleton public class StoreRepository extends BaseRepository { private final StoreApi storeApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java index 7aef86a2..bfdf9e39 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java @@ -11,6 +11,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing supplier data. + */ @Singleton public class SupplierRepository extends BaseRepository { private final SupplierApi supplierApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java index 0ed9ced9..bfa9cc23 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java @@ -10,6 +10,9 @@ import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Singleton; +/** + * Repository class for managing user profile and account data. + */ @Singleton public class UserRepository extends BaseRepository { private final UserApi userApi; diff --git a/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java b/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java index 90113cac..46fc5f4d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java +++ b/android/app/src/main/java/com/example/petstoremobile/services/ChatNotificationService.java @@ -30,7 +30,9 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -// Service to receive notifications when a new conversation is created +/** + * Service to receive and display notifications when a new chat conversation or message is created. + */ @AndroidEntryPoint public class ChatNotificationService extends Service { private static final String TAG = "ChatNotificationService"; diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java b/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java index b58f38d4..4f3e62ad 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java @@ -8,11 +8,17 @@ import com.prolificinteractive.materialcalendarview.spans.DotSpan; import java.util.Collection; import java.util.HashSet; +/** + * Decorator for MaterialCalendarView to highlight specific dates with a colored dot. + */ public class EventDecorator implements DayViewDecorator { private final int color; private final HashSet dates; + /** + * Initializes the decorator with a color and a collection of dates to highlight. + */ public EventDecorator(int color, Collection dates) { this.color = color; this.dates = new HashSet<>(dates); diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java index bf45f4f8..174b23f5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java @@ -8,7 +8,14 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; +/** + * Utility class for file operations, particularly handling Uris from the Android content system. + */ public class FileUtils { + /** + * Creates a temporary file in the cache directory from a given Uri. + * This allows the app to work with a local file path instead of a content stream. + */ public static File getFileFromUri(Context context, Uri uri) { try { if ("content".equals(uri.getScheme())) { @@ -48,6 +55,9 @@ public class FileUtils { } } + /** + * Retrieves the display name of a file from its Uri. + */ public static String getFileName(Context context, Uri uri) { String result = null; if (uri.getScheme().equals("content")) { diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java b/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java index 197cb557..f651c25a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java @@ -5,7 +5,7 @@ import java.util.List; /** * Helper class to manage selection state in Adapters for bulk operations. - * Uses String keys to support both simple Long IDs and composite keys (e.g., "id1-id2"). + * Uses String keys to support both Long IDs and composite keys. */ public class SelectionHelper { @@ -13,15 +13,32 @@ public class SelectionHelper { private boolean selectionMode = false; private final SelectionListener listener; + /** + * Listener interface to observe selection state changes. + */ public interface SelectionListener { + /** + * Called when the number of selected items changes. + */ void onSelectionChanged(int count); + + /** + * Called when selection mode is enabled or disabled. + */ void onSelectionModeToggle(boolean selectionMode); } + /** + * Initializes the helper with a listener. + */ public SelectionHelper(SelectionListener listener) { this.listener = listener; } + /** + * Toggles the selection state of a specific key. + * Automatically exits selection mode if the last item is deselected. + */ public void toggleSelection(String key) { if (key == null) return; @@ -39,6 +56,9 @@ public class SelectionHelper { } } + /** + * Enters selection mode and selects the specified key. + */ public void startSelection(String key) { if (key == null) return; selectionMode = true; @@ -47,18 +67,30 @@ public class SelectionHelper { listener.onSelectionModeToggle(true); } + /** + * Checks if a specific key is currently selected. + */ public boolean isSelected(String key) { return selectedKeys.contains(key); } + /** + * Checks if the helper is currently in selection mode. + */ public boolean isInSelectionMode() { return selectionMode; } + /** + * Returns a copy of the list of currently selected keys. + */ public List getSelectedKeys() { return new ArrayList<>(selectedKeys); } + /** + * Clears all selections and exits selection mode. + */ public void clearSelection() { selectedKeys.clear(); selectionMode = false; diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java index de1e6304..2273b227 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java @@ -22,7 +22,7 @@ import java.util.function.Function; public class SpinnerUtils { /** - * Populates a spinner with a list of items and handles pre-selection. + * Populates a spinner with generic data and handles pre-selection based on an ID. */ public static void populateSpinner(Context context, Spinner spinner, List data, Function nameExtractor, String defaultText, @@ -31,7 +31,7 @@ public class SpinnerUtils { } /** - * Populates a spinner with white text (for dark backgrounds). + * Populates a spinner with white text, suitable for dark backgrounds or overlays. */ public static void populateWhiteSpinner(Context context, Spinner spinner, List data, Function nameExtractor, String defaultText, @@ -39,6 +39,9 @@ public class SpinnerUtils { populateSpinnerWithAdapter(context, spinner, data, nameExtractor, defaultText, preselectedId, idExtractor, true); } + /** + * Internal helper to populate a spinner with the appropriate adapter and handle pre-selection. + */ private static void populateSpinnerWithAdapter(Context context, Spinner spinner, List data, Function nameExtractor, String defaultText, Long preselectedId, Function idExtractor, diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java index d944de83..453ddd3b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java @@ -44,16 +44,37 @@ public class ActivityLogListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of activity logs. + */ public LiveData> getLogs() { return logs; } + + /** + * Returns the LiveData for the store dropdown options. + */ public LiveData> getStoreOptions() { return storeOptions; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of logs has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads initial data for stores and logs. + */ public void loadInitialData() { loadStores(); loadLogs(true); } + /** + * Loads store options for filtering. + */ private void loadStores() { observeOnce(storeRepository.getStoreDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -62,6 +83,9 @@ public class ActivityLogListViewModel extends ViewModel { }); } + /** + * Loads activity logs from the repository with current filters. + */ public void loadLogs(boolean reset) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -88,21 +112,33 @@ public class ActivityLogListViewModel extends ViewModel { }); } + /** + * Sets the role filter and reloads logs. + */ public void setRoleFilter(String role) { currentRole = "All Roles".equals(role) ? null : role; loadLogs(true); } + /** + * Sets the store filter and reloads logs. + */ public void setStoreFilter(Long storeId) { currentStoreId = storeId; loadLogs(true); } + /** + * Sets the search query and reloads logs. + */ public void setSearchQuery(String query) { currentSearch = (query == null || query.trim().isEmpty()) ? null : query.trim(); loadLogs(true); } + /** + * Sets the date range filter and reloads logs. + */ public void setDateRange(String startDate, String endDate) { currentStartDate = startDate; currentEndDate = endDate; @@ -110,6 +146,9 @@ public class ActivityLogListViewModel extends ViewModel { } + /** + * Observes a LiveData once, removing the observer after the first response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java index 7416c0fc..c25ce797 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java @@ -48,24 +48,39 @@ public class AdoptionDetailViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Sets the adoption ID and initializes the view mode. + */ public void setAdoptionId(long id) { this.adoptionId = id; initMode(id != -1); } + /** + * Returns the current adoption ID. + */ public long getAdoptionId() { return adoptionId; } + /** + * Checks if the fragment is currently in editing mode. + */ public boolean isEditing() { ViewState current = viewState.getValue(); return current != null && current.isEditing; } + /** + * Returns the LiveData for the current view state. + */ public LiveData getViewState() { return viewState; } + /** + * Initializes the view state based on whether the mode is editing or adding. + */ public void initMode(boolean isEditing) { updateViewState(state -> { state.isEditing = isEditing; @@ -94,6 +109,9 @@ public class AdoptionDetailViewModel extends ViewModel { }); } + /** + * Loads initial data for the form spinners. + */ public void loadInitialFormData(boolean isEditing) { // Pets are loaded dynamically based on store selection; no pre-load needed. observeOnce(customerRepository.getCustomerDropdowns(), r -> { @@ -108,6 +126,9 @@ public class AdoptionDetailViewModel extends ViewModel { }); } + /** + * Handles customer selection and updates the view state. + */ public void onCustomerSelected(int position) { List list = customerList.getValue(); Long customerId = (position > 0 && list != null && position <= list.size()) @@ -115,6 +136,9 @@ public class AdoptionDetailViewModel extends ViewModel { updateViewState(state -> state.selectedCustomerId = customerId); } + /** + * Handles store selection, updates the view state, and loads related employees and pets. + */ public void onStoreSelected(int position) { List list = storeList.getValue(); if (position > 0 && list != null && position <= list.size()) { @@ -141,6 +165,9 @@ public class AdoptionDetailViewModel extends ViewModel { } } + /** + * Handles pet selection, updates the view state, and loads the pet's price. + */ public void onPetSelected(int position) { List list = petList.getValue(); if (position > 0 && list != null && position <= list.size()) { @@ -155,6 +182,9 @@ public class AdoptionDetailViewModel extends ViewModel { } } + /** + * Loads available pets for the selected store. + */ private void loadAvailablePetsByStore(Long storeId) { observeOnce(petRepository.getAdoptionPets(storeId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { @@ -163,6 +193,9 @@ public class AdoptionDetailViewModel extends ViewModel { }); } + /** + * Loads the price for a specific pet. + */ private void loadPetPrice(Long petId) { observeOnce(petRepository.getPetById(petId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { @@ -181,6 +214,9 @@ public class AdoptionDetailViewModel extends ViewModel { }); } + /** + * Loads employees assigned to a specific store. + */ private void loadEmployeesForStore(Long storeId) { observeOnce(storeRepository.getStoreEmployees(storeId), r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { @@ -190,7 +226,7 @@ public class AdoptionDetailViewModel extends ViewModel { } /** - * Called when the date or status changes in the UI. Applies date-based field enabling. + * Called when the date or status changes in the UI. */ public void onDateChanged(String date, String currentStatus) { updateViewState(s -> { @@ -203,7 +239,7 @@ public class AdoptionDetailViewModel extends ViewModel { s.selectedStatus = s.availableStatuses[0]; } - if (!s.isEditing) return; // add mode: field enabling handled separately + if (!s.isEditing) return; boolean isPast = DateTimeUtils.isDateBeforeToday(date); if (isPast) { @@ -222,6 +258,9 @@ public class AdoptionDetailViewModel extends ViewModel { }); } + /** + * Calculates available statuses based on the date and mode. + */ private String[] calculateAvailableStatuses(boolean isEditing, String date) { if (!isEditing) return new String[]{"Pending"}; if (date == null || date.isEmpty()) return new String[]{}; @@ -236,9 +275,11 @@ public class AdoptionDetailViewModel extends ViewModel { s.isPetEnabled = enabled; s.isEmployeeEnabled = enabled; s.isDateEnabled = enabled; - // fee never editable } + /** + * Fetches adoption details from the repository. + */ public LiveData> loadAdoption() { MutableLiveData> result = new MutableLiveData<>(); observeOnce(adoptionRepository.getAdoptionById(adoptionId), resource -> { @@ -292,6 +333,9 @@ public class AdoptionDetailViewModel extends ViewModel { return result; } + /** + * Saves or updates the adoption record. + */ public LiveData> saveAdoption(AdoptionDTO dto) { if (isEditing()) { return adoptionRepository.updateAdoption(adoptionId, dto); @@ -299,17 +343,41 @@ public class AdoptionDetailViewModel extends ViewModel { return adoptionRepository.createAdoption(dto); } + /** + * Deletes the current adoption record. + */ public LiveData> deleteAdoption() { return adoptionRepository.deleteAdoption(adoptionId); } + /** + * Returns the LiveData for the pet dropdown list. + */ public LiveData> getPetList() { return petList; } + + /** + * Returns the LiveData for the customer dropdown list. + */ public LiveData> getCustomerList() { return customerList; } + + /** + * Returns the LiveData for the store dropdown list. + */ public LiveData> getStoreList() { return storeList; } + + /** + * Returns the LiveData for the employee dropdown list. + */ public LiveData> getEmployeeList() { return employeeList; } + /** + * Updates the employee list. + */ public void setEmployeeList(List list) { employeeList.setValue(list); } + /** + * Helper to update the view state atomically. + */ private void updateViewState(Action action) { ViewState current = viewState.getValue(); if (current != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java index 005198d1..dbfc262b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionListViewModel.java @@ -39,11 +39,29 @@ public class AdoptionListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of adoptions. + */ public LiveData> getAdoptions() { return adoptions; } + + /** + * Returns the LiveData for the list of stores for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of adoptions has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads adoptions from the repository with the specified filters. + */ public void loadAdoptions(boolean reset, String query, String status, Long storeId, String date, Long employeeId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -73,6 +91,9 @@ public class AdoptionListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -93,6 +114,9 @@ public class AdoptionListViewModel extends ViewModel { }); } + /** + * Deletes multiple adoptions by their IDs. + */ public LiveData> bulkDeleteAdoptions(List ids) { return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java index 4a9b94d9..95c8979f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java @@ -28,6 +28,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for retrieving and providing data for the analytics dashboard. + */ @HiltViewModel public class AnalyticsViewModel extends ViewModel { private final SaleRepository saleRepository; @@ -50,12 +53,34 @@ public class AnalyticsViewModel extends ViewModel { this.tokenManager = tokenManager; } + /** + * Returns the LiveData for the analytics data. + */ public LiveData getAnalyticsData() { return analyticsData; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Returns the LiveData for error messages. + */ public LiveData getErrorMessage() { return errorMessage; } + + /** + * Returns the LiveData for available payment method filters. + */ public LiveData> getAvailablePaymentMethods() { return availablePaymentMethods; } + + /** + * Returns the LiveData for available store filters. + */ public LiveData> getAvailableStores() { return availableStores; } + /** + * Triggers loading of analytics data with current filters. + */ public void loadAnalytics() { isLoading.setValue(true); errorMessage.setValue(null); @@ -75,35 +100,56 @@ public class AnalyticsViewModel extends ViewModel { }); } + /** + * Applies a new filter state and re-computes analytics. + */ public void applyFilter(FilterState filter) { currentFilter = filter; applyCurrentFilter(); } + /** + * Resets filters to default values and reset analytics. + */ public void resetFilter() { currentFilter = new FilterState(); storeFilter = "All Stores"; applyCurrentFilter(); } + /** + * Sets the view mode + */ public void setViewMode(String mode) { viewMode = mode; applyCurrentFilter(); } + /** + * Returns the current view mode. + */ public String getViewMode() { return viewMode; } + /** + * Sets the store filter + */ public void setStoreFilter(String store) { storeFilter = (store != null && !store.isEmpty()) ? store : "All Stores"; applyCurrentFilter(); } + /** + * Returns the current store filter. + */ public String getStoreFilter() { return storeFilter; } + /** + * Applies the current filters + */ private void applyCurrentFilter() { List salesForMode; if (viewMode.equals("mine")) { @@ -124,6 +170,9 @@ public class AnalyticsViewModel extends ViewModel { computeAnalytics(filtered, currentFilter); } + /** + * Extracts unique store names from the cached sales data for filtering. + */ private void deriveStores() { java.util.Set stores = new java.util.TreeSet<>(); for (SaleDTO s : cachedSales) { @@ -137,6 +186,9 @@ public class AnalyticsViewModel extends ViewModel { availableStores.setValue(result); } + /** + * Extracts unique payment methods from the cached sales data for filtering. + */ private void derivePaymentMethods() { java.util.Set methods = new java.util.TreeSet<>(); for (SaleDTO s : cachedSales) { @@ -150,6 +202,9 @@ public class AnalyticsViewModel extends ViewModel { availablePaymentMethods.setValue(result); } + /** + * Filters a list of sales based on date range and payment method. + */ private List filterSales(List sales, FilterState filter) { List result = new ArrayList<>(); for (SaleDTO s : sales) { @@ -165,6 +220,9 @@ public class AnalyticsViewModel extends ViewModel { return result; } + /** + * Computesanalytics data from a filtered list of sales. + */ private void computeAnalytics(List sales, FilterState filter) { List regularSales = new ArrayList<>(); for (SaleDTO s : sales) { @@ -258,6 +316,9 @@ public class AnalyticsViewModel extends ViewModel { analyticsData.setValue(data); } + /** + * Returns a date string for the specified offset from today. + */ private String todayString(int offsetDays) { Calendar c = Calendar.getInstance(); c.add(Calendar.DAY_OF_YEAR, offsetDays); @@ -265,6 +326,9 @@ public class AnalyticsViewModel extends ViewModel { c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH)); } + /** + * Shifts a date string by a specified number of days. + */ private String shiftDate(String date, int offsetDays) { try { String[] p = date.split("-"); @@ -278,6 +342,9 @@ public class AnalyticsViewModel extends ViewModel { } } + /** + * Generates a list of date strings between the start and end dates. + */ private List buildDateRange(String start, String end, int maxDays) { List dates = new ArrayList<>(); try { @@ -298,6 +365,9 @@ public class AnalyticsViewModel extends ViewModel { return dates; } + /** + * Builds a title for the daily revenue chart based on the date range. + */ private String buildDailyTitle(FilterState filter, String rangeStart, String rangeEnd) { if (filter.startDate.isEmpty() && filter.endDate.isEmpty()) return "Daily Revenue (Last 7 Days)"; String s = rangeStart.length() >= 10 ? rangeStart.substring(5) : rangeStart; @@ -305,6 +375,9 @@ public class AnalyticsViewModel extends ViewModel { return "Daily Revenue (" + s + " – " + e + ")"; } + /** + * Observes a LiveData once, removing the observer after the first non-loading response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java index b229c3a0..c96e21de 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java @@ -387,7 +387,9 @@ public class AppointmentDetailViewModel extends ViewModel { void run(T t); } - /** Observes a LiveData once, removing the observer after the first non-loading response. */ + /** + * Observes a LiveData once, removing the observer after the first non-loading response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java index 19924349..30aa2511 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentListViewModel.java @@ -39,11 +39,29 @@ public class AppointmentListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of appointments. + */ public LiveData> getAppointments() { return appointments; } + + /** + * Returns the LiveData for the list of stores for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of appointments has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads appointments from the repository with the specified filters. + */ public void loadAppointments(boolean reset, String query, String status, Long storeId, String date, Long employeeId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -71,6 +89,9 @@ public class AppointmentListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -91,6 +112,9 @@ public class AppointmentListViewModel extends ViewModel { }); } + /** + * Deletes multiple appointments by their IDs. + */ public LiveData> bulkDeleteAppointments(List ids) { return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java index 36e437bb..cc05eb3e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java @@ -16,6 +16,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; import okhttp3.MultipartBody; +/** + * ViewModel for managing user authentication and profile data. + */ @HiltViewModel public class AuthViewModel extends ViewModel { private final AuthRepository repository; @@ -26,42 +29,42 @@ public class AuthViewModel extends ViewModel { } /** - * Authenticates a user with username and password. + * Attempts to log in the user with the provided credentials. */ public LiveData> login(String username, String password) { return repository.login(new AuthDTO.LoginRequest(username, password)); } /** - * Retrieves the profile information of the currently authenticated user. + * Retrieves the current user's profile information. */ public LiveData> getMe() { return repository.getMe(); } /** - * Updates the profile information of the current user. + * Updates the current user's profile information. */ public LiveData> updateMe(Map updates) { return repository.updateMe(updates); } /** - * Uploads a new avatar image for the current user. + * Uploads a new avatar for the current user. */ public LiveData> uploadAvatar(MultipartBody.Part avatar) { return repository.uploadAvatar(avatar); } /** - * Deletes the avatar image of the current user. + * Deletes the current user's avatar. */ public LiveData> deleteAvatar() { return repository.deleteAvatar(); } /** - * Logs out the current user by clearing stored credentials. + * Logs out the user by clearing the session. */ public void logout() { repository.logout(); diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatListViewModel.java index 4d14a3b4..0010d227 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatListViewModel.java @@ -29,6 +29,9 @@ import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; +/** + * ViewModel for managing the list of chat conversations and real-time message updates. + */ @HiltViewModel public class ChatListViewModel extends ViewModel { private final ChatRepository chatRepository; @@ -48,19 +51,43 @@ public class ChatListViewModel extends ViewModel { this.customerRepository = customerRepository; } + /** + * Returns the LiveData for the list of active chats. + */ public LiveData> getActiveChats() { return activeChats; } + + /** + * Returns the LiveData for the list of closed chats. + */ public LiveData> getClosedChats() { return closedChats; } + + /** + * Returns the LiveData for the list of messages in the current conversation. + */ public LiveData> getMessageList() { return messageList; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + /** + * Returns the ID of the last active conversation. + */ public Long getLastActiveConversationId() { return lastActiveConversationId; } + /** + * Sets the ID of the last active conversation. + */ public void setLastActiveConversationId(Long conversationId) { this.lastActiveConversationId = conversationId; } + /** + * Loads the list of customers for name mapping. + */ public void loadCustomers() { isLoading.setValue(true); customerRepository.getAllCustomers(0, 100).observeForever(resource -> { @@ -75,6 +102,9 @@ public class ChatListViewModel extends ViewModel { }); } + /** + * Loads all chat conversations from the repository. + */ public void loadConversations() { isLoading.setValue(true); chatRepository.getAllConversations().observeForever(resource -> { @@ -99,6 +129,9 @@ public class ChatListViewModel extends ViewModel { }); } + /** + * Loads message history for a specific conversation. + */ public void loadMessageHistory(Long conversationId) { isLoading.setValue(true); chatRepository.getMessages(conversationId).observeForever(resource -> { @@ -115,22 +148,37 @@ public class ChatListViewModel extends ViewModel { }); } + /** + * Sends a text message in a specific conversation. + */ public LiveData> sendMessage(Long conversationId, String text) { return chatRepository.sendMessage(conversationId, new SendMessageRequest(text)); } + /** + * Sends a message with an attachment in a specific conversation. + */ public LiveData> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) { return chatRepository.sendMessageWithAttachment(conversationId, content, attachment); } + /** + * Downloads an attachment for a specific message. + */ public LiveData> downloadAttachment(Long messageId) { return chatRepository.downloadAttachment(messageId); } + /** + * Closes an active chat conversation. + */ public LiveData> closeConversation(Long conversationId) { return chatRepository.updateConversationStatus(conversationId, new UpdateConversationStatusRequest("CLOSED")); } + /** + * Adds a message to the local list (used for real-time updates). + */ public void addMessageLocally(MessageDTO dto) { List current = new ArrayList<>(messageList.getValue()); if (dto.getId() != null) { @@ -142,6 +190,9 @@ public class ChatListViewModel extends ViewModel { messageList.setValue(current); } + /** + * Updates a conversation's status locally. + */ public void updateConversationLocally(ConversationDTO dto) { updateList(activeChats, dto); updateList(closedChats, dto); @@ -180,6 +231,9 @@ public class ChatListViewModel extends ViewModel { return m; } + /** + * Retrieves a customer's name by their ID. + */ public String getCustomerName(Long customerId) { return customerNames.getOrDefault(customerId, "Customer #" + customerId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponDetailViewModel.java index 49f52ef5..93541676 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponDetailViewModel.java @@ -23,23 +23,38 @@ public class CouponDetailViewModel extends ViewModel { this.repository = repository; } + /** + * Loads the coupon details from the repository. + */ public LiveData> loadCoupon(long id) { return repository.getCouponById(id); } + /** + * Sets the current coupon ID and editing mode. + */ public void setCouponId(long id, boolean isEditing) { this.couponId = id; this.isEditing = isEditing; } + /** + * Returns the current coupon ID. + */ public long getCouponId() { return couponId; } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { return isEditing; } + /** + * Saves or updates the coupon record. + */ public LiveData> saveCoupon(CouponDTO dto) { if (isEditing && couponId > 0) { return repository.updateCoupon(couponId, dto); @@ -48,6 +63,9 @@ public class CouponDetailViewModel extends ViewModel { } } + /** + * Deletes the current coupon record. + */ public LiveData> deleteCoupon() { return repository.deleteCoupon(couponId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java index d825b935..eeb47f16 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CouponListViewModel.java @@ -17,6 +17,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing the list of coupons. + */ @HiltViewModel public class CouponListViewModel extends ViewModel { private final CouponRepository repository; @@ -32,10 +35,24 @@ public class CouponListViewModel extends ViewModel { this.repository = repository; } + /** + * Returns the LiveData for the list of coupons. + */ public LiveData> getCoupons() { return coupons; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of coupons has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads coupons from the repository with the specified filters. + */ public void loadCoupons(boolean reset, Boolean active, String discountType, String sort) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -75,6 +92,9 @@ public class CouponListViewModel extends ViewModel { }); } + /** + * Deletes multiple coupons by their IDs. + */ public LiveData> bulkDeleteCoupons(List ids) { return repository.bulkDeleteCoupons(ids); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerDetailViewModel.java index 0d0ebfaf..1604c9e8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerDetailViewModel.java @@ -23,6 +23,9 @@ public class CustomerDetailViewModel extends ViewModel { this.repository = repository; } + /** + * Sets the current customer ID and editing mode. + */ public void setCustomerId(long id, boolean isEditing) { this.customerId = id; this.isEditing = isEditing; @@ -48,6 +51,9 @@ public class CustomerDetailViewModel extends ViewModel { return repository.getCustomerById(id); } + /** + * Saves or updates the customer record. + */ public LiveData> saveCustomer(CustomerDTO dto) { if (isEditing && customerId > 0) { return repository.updateCustomer(customerId, dto); @@ -56,6 +62,9 @@ public class CustomerDetailViewModel extends ViewModel { } } + /** + * Deletes the current customer record. + */ public LiveData> deleteCustomer() { return repository.deleteCustomer(customerId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java index a216c47a..2fd7393b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/CustomerListViewModel.java @@ -17,6 +17,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing the list of customers, including local filtering and pagination. + */ @HiltViewModel public class CustomerListViewModel extends ViewModel { private final CustomerRepository repository; @@ -37,10 +40,24 @@ public class CustomerListViewModel extends ViewModel { this.repository = repository; } + /** + * Returns the LiveData for the list of filtered customers. + */ public LiveData> getFilteredCustomers() { return filteredCustomers; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of customers has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads the full list of customers from the repository. + */ public void loadCustomers(boolean reset) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -69,6 +86,9 @@ public class CustomerListViewModel extends ViewModel { }); } + /** + * Observes a LiveData once, removing the observer after the first non-loading response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override @@ -81,12 +101,18 @@ public class CustomerListViewModel extends ViewModel { }); } + /** + * Filters the customer list locally based on query and status. + */ public void filter(String query, String status) { this.lastQuery = query; this.lastStatus = status; applyFilter(customers.getValue()); } + /** + * Applies the filters to the full list and updates the filtered LiveData. + */ private void applyFilter(List all) { if (all == null) return; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java index c872ea49..3bf33373 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java @@ -39,26 +39,48 @@ public class InventoryDetailViewModel extends ViewModel { this.productRepository = productRepository; } + /** + * Sets the inventory ID and initializes the editing mode. + */ public void setInventoryId(long id) { this.inventoryId = id; this.isEditing = id != -1; } + /** + * Returns the current inventory ID. + */ public long getInventoryId() { return inventoryId; } + + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { return isEditing; } + /** + * Loads the inventory record from the repository. + */ public LiveData> loadInventory() { return inventoryRepository.getInventoryById(inventoryId); } + /** + * Loads store dropdown options. + */ public LiveData>> loadStores() { return storeRepository.getStoreDropdowns(); } + /** + * Loads product dropdown options. + */ public LiveData>> loadProducts() { return productRepository.getProductDropdowns(); } + /** + * Saves or updates the inventory record. + */ public LiveData> saveInventory(InventoryDTO dto) { if (isEditing) { return inventoryRepository.updateInventory(inventoryId, dto); @@ -67,13 +89,30 @@ public class InventoryDetailViewModel extends ViewModel { } } + /** + * Deletes the current inventory record. + */ public LiveData> deleteInventory() { return inventoryRepository.deleteInventory(inventoryId); } + /** + * Updates the store list. + */ public void setStoreList(List list) { storeList.setValue(list); } + + /** + * Returns the LiveData for the store list. + */ public LiveData> getStoreList() { return storeList; } + /** + * Updates the product list. + */ public void setProductList(List list) { productList.setValue(list); } + + /** + * Returns the LiveData for the product list. + */ public LiveData> getProductList() { return productList; } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java index 66387983..8e482390 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryListViewModel.java @@ -39,11 +39,29 @@ public class InventoryListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the inventory list. + */ public LiveData> getInventory() { return inventory; } + + /** + * Returns the LiveData for the store list for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of inventory has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads inventory items from the repository with the specified filters. + */ public void loadInventory(boolean reset, String query, Long storeId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -69,6 +87,9 @@ public class InventoryListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -89,6 +110,9 @@ public class InventoryListViewModel extends ViewModel { }); } + /** + * Deletes multiple inventory items by their IDs. + */ public LiveData> bulkDeleteInventory(List ids) { return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java index 89f56d51..5c04e468 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetListViewModel.java @@ -21,6 +21,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing the list of pets, including filtering and bulk deletion. + */ @HiltViewModel public class PetListViewModel extends ViewModel { private final PetRepository petRepository; @@ -41,12 +44,34 @@ public class PetListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of pets. + */ public LiveData> getPets() { return pets; } + + /** + * Returns the LiveData for the list of stores for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for available species filters. + */ public LiveData> getSpeciesOptions() { return speciesOptions; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of pets has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads pets from the repository with the specified filters. + */ public void loadPets(boolean reset, String query, String status, String species, Long storeId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -79,6 +104,9 @@ public class PetListViewModel extends ViewModel { }); } + /** + * Loads the list of available species for filtering. + */ public void loadSpecies() { observeOnce(petRepository.getPetSpeciesDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -92,6 +120,9 @@ public class PetListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -100,6 +131,9 @@ public class PetListViewModel extends ViewModel { }); } + /** + * Observes a LiveData once, removing the observer after the first non-loading response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override @@ -112,6 +146,9 @@ public class PetListViewModel extends ViewModel { }); } + /** + * Deletes multiple pets by their IDs. + */ public LiveData> bulkDeletePets(List ids) { return petRepository.bulkDeletePets(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetProfileViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetProfileViewModel.java index 1fb75f1c..8366bf63 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetProfileViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetProfileViewModel.java @@ -21,14 +21,23 @@ public class PetProfileViewModel extends ViewModel { this.repository = repository; } + /** + * Retrieves a pet's details by its ID. + */ public LiveData> getPetById(Long id) { return repository.getPetById(id); } + /** + * Uploads an image for a specific pet. + */ public LiveData> uploadPetImage(Long id, MultipartBody.Part image) { return repository.uploadPetImage(id, image); } + /** + * Deletes the image for a specific pet. + */ public LiveData> deletePetImage(Long id) { return repository.deletePetImage(id); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java index c1ac5fdd..3c157dde 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java @@ -35,27 +35,45 @@ public class ProductDetailViewModel extends ViewModel { this.categoryRepository = categoryRepository; } + /** + * Sets the product ID and initializes the editing mode. + */ public void setProdId(long id) { this.prodId = id; this.isEditing = id != -1; } + /** + * Returns the current product ID. + */ public long getProdId() { return prodId; } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { return isEditing; } + /** + * Loads product category dropdown options. + */ public LiveData>> loadCategories() { return productRepository.getCategoryDropdowns(); } + /** + * Loads the product details from the repository. + */ public LiveData> loadProduct() { return productRepository.getProductById(prodId); } + /** + * Saves or updates the product record. + */ public LiveData> saveProduct(ProductDTO dto) { if (isEditing) { return productRepository.updateProduct(prodId, dto); @@ -64,22 +82,37 @@ public class ProductDetailViewModel extends ViewModel { } } + /** + * Deletes the current product record. + */ public LiveData> deleteProduct() { return productRepository.deleteProduct(prodId); } + /** + * Uploads an image for the current product. + */ public LiveData> uploadProductImage(MultipartBody.Part image) { return productRepository.uploadProductImage(prodId, image); } + /** + * Deletes the current product's image. + */ public LiveData> deleteProductImage() { return productRepository.deleteProductImage(prodId); } + /** + * Updates the category list. + */ public void setCategoryList(List list) { categoryList.setValue(list); } + /** + * Returns the LiveData for the category list. + */ public LiveData> getCategoryList() { return categoryList; } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java index b22dee6c..e242768f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductListViewModel.java @@ -38,11 +38,29 @@ public class ProductListViewModel extends ViewModel { this.categoryRepository = categoryRepository; } + /** + * Returns the LiveData for the list of products. + */ public LiveData> getProducts() { return products; } + + /** + * Returns the LiveData for the list of categories for filtering. + */ public LiveData> getCategories() { return categories; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of products has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads products from the repository with the specified filters. + */ public void loadProducts(boolean reset, String query, Long categoryId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -70,6 +88,9 @@ public class ProductListViewModel extends ViewModel { }); } + /** + * Loads the list of categories for filtering options. + */ public void loadCategories() { observeOnce(productRepository.getCategoryDropdowns(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java index 2cbf5036..66d0201e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java @@ -40,28 +40,54 @@ public class ProductSupplierDetailViewModel extends ViewModel { this.supplierRepository = supplierRepository; } + /** + * Sets the composite key for the product-supplier relationship and enables editing mode. + */ public void setEditMode(long productId, long supplierId) { this.isEditing = true; this.editProductId = productId; this.editSupplierId = supplierId; } + /** + * Loads the product-supplier details from the repository. + */ public LiveData> loadProductSupplier() { return psRepository.getProductSupplierById(editProductId, editSupplierId); } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { return isEditing; } + + /** + * Returns the product ID for the relationship being edited. + */ public long getEditProductId() { return editProductId; } + + /** + * Returns the supplier ID for the relationship being edited. + */ public long getEditSupplierId() { return editSupplierId; } + /** + * Loads products for the dropdown selection. + */ public LiveData>> loadProducts() { return productRepository.getAllProducts(null, null, 0, 200, "prodName"); } + /** + * Loads suppliers for the dropdown selection. + */ public LiveData>> loadSuppliers() { return supplierRepository.getAllSuppliers(0, 200, null, "supCompany"); } + /** + * Saves or updates the product-supplier relationship. + */ public LiveData> saveProductSupplier(ProductSupplierDTO dto) { if (isEditing) { return psRepository.updateProductSupplier(editProductId, editSupplierId, dto); @@ -70,13 +96,30 @@ public class ProductSupplierDetailViewModel extends ViewModel { } } + /** + * Deletes the current product-supplier relationship. + */ public LiveData> deleteProductSupplier() { return psRepository.deleteProductSupplier(editProductId, editSupplierId); } + /** + * Updates the product list. + */ public void setProductList(List list) { productList.setValue(list); } + + /** + * Returns the LiveData for the product list. + */ public LiveData> getProductList() { return productList; } + /** + * Updates the supplier list. + */ public void setSupplierList(List list) { supplierList.setValue(list); } + + /** + * Returns the LiveData for the supplier list. + */ public LiveData> getSupplierList() { return supplierList; } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java index 930770a4..0a568e51 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierListViewModel.java @@ -44,12 +44,34 @@ public class ProductSupplierListViewModel extends ViewModel { this.supplierRepository = supplierRepository; } + /** + * Returns the LiveData for the product-supplier relationship list. + */ public LiveData> getProductSuppliers() { return productSuppliers; } + + /** + * Returns the LiveData for the list of products for filtering. + */ public LiveData> getProducts() { return products; } + + /** + * Returns the LiveData for the list of suppliers for filtering. + */ public LiveData> getSuppliers() { return suppliers; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of product-supplier relationships has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads product-supplier relationships from the repository with the specified filters. + */ public void loadProductSuppliers(boolean reset, String query, Long productId, Long supplierId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -77,6 +99,9 @@ public class ProductSupplierListViewModel extends ViewModel { }); } + /** + * Loads products and suppliers for filtering options. + */ public void loadFilterData() { observeOnce(productRepository.getAllProducts(null, null, 0, 100, "prodName"), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -103,6 +128,9 @@ public class ProductSupplierListViewModel extends ViewModel { }); } + /** + * Deletes multiple product-supplier relationships by their composite keys. + */ public LiveData> bulkDeleteProductSuppliers(List ids) { return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java index 436cfa4c..c68e1d89 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java @@ -20,6 +20,9 @@ public class PurchaseOrderDetailViewModel extends ViewModel { this.repository = repository; } + /** + * Loads the purchase order details from the repository. + */ public LiveData> loadPurchaseOrder(long id) { return repository.getPurchaseOrderById(id); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java index 923d336e..3febfe0e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderListViewModel.java @@ -38,11 +38,29 @@ public class PurchaseOrderListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of purchase orders. + */ public LiveData> getPurchaseOrders() { return purchaseOrders; } + + /** + * Returns the LiveData for the list of stores for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of purchase orders has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads purchase orders from the repository with the specified filters. + */ public void loadPurchaseOrders(boolean reset, String query, Long storeId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -70,6 +88,9 @@ public class PurchaseOrderListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java index ff3d069e..bf96e89b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java @@ -20,6 +20,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for processing and managing refund requests. + */ @HiltViewModel public class RefundViewModel extends ViewModel { private final SaleRepository saleRepository; @@ -34,35 +37,59 @@ public class RefundViewModel extends ViewModel { this.saleRepository = saleRepository; } + /** + * Loads all sales from the repository for refund selection. + */ public LiveData>> loadAllSales() { return saleRepository.getAllSales(0, 1000, null, null, null, null, null, "saleDate,desc"); } + /** + * Sets the list of all available sales. + */ public void setAllSales(List sales) { allSales.setValue(sales); } + /** + * Returns the list of all available sales. + */ public List getAllSalesList() { return allSales.getValue(); } + /** + * Sets the current sale being processed for a refund and computes refundable items. + */ public void setCurrentSale(SaleDTO sale) { currentSale.setValue(sale); buildRefundableItems(); } + /** + * Returns the current sale being processed. + */ public SaleDTO getCurrentSale() { return currentSale.getValue(); } + /** + * Returns the LiveData for the list of items available for refund in the current sale. + */ public LiveData> getAvailableItems() { return availableItems; } + /** + * Returns the LiveData for the items added to the refund cart. + */ public LiveData> getRefundCart() { return refundCart; } + /** + * Builds the list of items available for refund, accounting for previous refunds. + */ private void buildRefundableItems() { SaleDTO sale = currentSale.getValue(); List sales = allSales.getValue(); @@ -103,6 +130,9 @@ public class RefundViewModel extends ViewModel { refundCart.setValue(new ArrayList<>()); } + /** + * Adds an item to the refund cart. + */ public void addToCart(RefundItem item, int qty) { List cart = new ArrayList<>(refundCart.getValue()); boolean merged = false; @@ -120,12 +150,18 @@ public class RefundViewModel extends ViewModel { refundCart.setValue(cart); } + /** + * Removes an item from the refund cart. + */ public void removeFromCart(RefundItem item) { List cart = new ArrayList<>(refundCart.getValue()); cart.remove(item); refundCart.setValue(cart); } + /** + * Calculates the total refund amount, proportional to the original total. + */ public BigDecimal calculateRefundTotal() { SaleDTO sale = currentSale.getValue(); List cart = refundCart.getValue(); @@ -153,6 +189,9 @@ public class RefundViewModel extends ViewModel { return originalTotal.abs().multiply(ratio).setScale(2, RoundingMode.HALF_UP); } + /** + * Submits the refund transaction to the repository. + */ public LiveData> submitRefund(String paymentMethod) { SaleDTO sale = currentSale.getValue(); List cart = refundCart.getValue(); diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java index 68369cce..4160659e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java @@ -24,6 +24,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing sale details, including creating new sales and viewing existing ones. + */ @HiltViewModel public class SaleDetailViewModel extends ViewModel { private final SaleRepository saleRepository; @@ -55,77 +58,150 @@ public class SaleDetailViewModel extends ViewModel { this.couponRepository = couponRepository; } + /** + * Sets the sale ID and whether the view is read-only. + */ public void setSaleId(long id, boolean viewOnly) { this.saleId = id; this.viewOnly = viewOnly; } + /** + * Returns the current sale ID. + */ public long getSaleId() { return saleId; } + + /** + * Returns true if the view is in read-only mode. + */ public boolean isViewOnly() { return viewOnly; } + /** + * Loads the details of a specific sale by its ID. + */ public LiveData> loadSaleDetails() { return saleRepository.getSaleById(saleId); } + /** + * Loads the list of stores for selection. + */ public LiveData>> loadStores() { return storeRepository.getStoreDropdowns(); } + /** + * Loads the list of customers for selection. + */ public LiveData>> loadCustomers() { return customerRepository.getCustomerDropdowns(); } + /** + * Loads the list of products for sale selection. + */ public LiveData>> loadProducts() { return productRepository.getAllProducts(null, null, 0, 200, null); } + /** + * Submits a new sale to the repository. + */ public LiveData> createSale(SaleDTO sale) { return saleRepository.createSale(sale); } + /** + * Sets the local list of stores. + */ public void setStoreList(List list) { storeList.setValue(list); } + + /** + * Returns the LiveData for the local list of stores. + */ public LiveData> getStoreList() { return storeList; } + /** + * Sets the local list of customers. + */ public void setCustomerList(List list) { customerList.setValue(list); } + + /** + * Returns the LiveData for the local list of customers. + */ public LiveData> getCustomerList() { return customerList; } + /** + * Sets the local list of products. + */ public void setProductList(List list) { productList.setValue(list); } + + /** + * Returns the LiveData for the local list of products. + */ public LiveData> getProductList() { return productList; } + /** + * Adds an item to the sale cart. + */ public void addToCart(SaleDTO.SaleItemDTO item) { List currentCart = new ArrayList<>(cartItems.getValue()); currentCart.add(item); cartItems.setValue(currentCart); } + /** + * Removes an item from the sale cart by product ID. + */ public void removeFromCart(Long prodId) { List currentCart = new ArrayList<>(cartItems.getValue()); currentCart.removeIf(item -> item.getProdId().equals(prodId)); cartItems.setValue(currentCart); } + /** + * Returns the LiveData for the list of items in the cart. + */ public LiveData> getCartItems() { return cartItems; } + /** + * Looks up a coupon by its code. + */ public LiveData> lookupCoupon(String code) { return couponRepository.getCouponByCode(code); } + /** + * Sets the currently applied coupon. + */ public void setAppliedCoupon(CouponDTO coupon) { appliedCoupon.setValue(coupon); } + /** + * Sets whether to use loyalty points for the current sale. + */ public void setUseLoyaltyPoints(boolean use) { useLoyaltyPoints.setValue(use); } + /** + * Returns the LiveData for whether loyalty points are being used. + */ public LiveData getUseLoyaltyPoints() { return useLoyaltyPoints; } + /** + * Returns the LiveData for the selected customer's full data. + */ public LiveData getSelectedCustomerData() { return selectedCustomerData; } + /** + * Selects a customer and loads their full data. + */ public void selectCustomer(Long customerId) { if (customerId == null) { selectedCustomerData.setValue(null); @@ -142,23 +218,38 @@ public class SaleDetailViewModel extends ViewModel { }); } + /** + * Clears the currently applied coupon. + */ public void clearCoupon() { appliedCoupon.setValue(null); } + /** + * Returns the LiveData for the applied coupon. + */ public LiveData getAppliedCoupon() { return appliedCoupon; } + /** + * Returns the ID of the applied coupon. + */ public Long getAppliedCouponId() { CouponDTO coupon = appliedCoupon.getValue(); return coupon != null ? coupon.getCouponId() : null; } + /** + * Calculates the total discount (coupon + loyalty). + */ public BigDecimal calculateDiscount() { return calculateCouponDiscount().add(calculateLoyaltyDiscount()); } + /** + * Calculates the discount from the applied coupon. + */ public BigDecimal calculateCouponDiscount() { CouponDTO coupon = appliedCoupon.getValue(); if (coupon == null || coupon.getDiscountValue() == null) return BigDecimal.ZERO; @@ -170,6 +261,9 @@ public class SaleDetailViewModel extends ViewModel { } } + /** + * Calculates the discount from using loyalty points. + */ public BigDecimal calculateLoyaltyDiscount() { if (Boolean.FALSE.equals(useLoyaltyPoints.getValue())) return BigDecimal.ZERO; CustomerDTO customer = selectedCustomerData.getValue(); @@ -184,11 +278,17 @@ public class SaleDetailViewModel extends ViewModel { return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05)).setScale(2, java.math.RoundingMode.HALF_UP); } + /** + * Calculates the number of loyalty points to be used. + */ public int calculatePointsToUse() { BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(); return loyaltyDiscount.divide(BigDecimal.valueOf(0.05), 0, java.math.RoundingMode.HALF_UP).intValue(); } + /** + * Calculates the subtotal of all items in the cart. + */ public BigDecimal calculateSubtotal() { BigDecimal total = BigDecimal.ZERO; List items = cartItems.getValue(); @@ -206,10 +306,16 @@ public class SaleDetailViewModel extends ViewModel { return total; } + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + /** + * Sets the loading state. + */ public void setLoading(boolean loading) { isLoading.setValue(loading); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java index aee0f948..e3f4f7ee 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleListViewModel.java @@ -22,6 +22,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing the list of sales transactions and applying filters. + */ @HiltViewModel public class SaleListViewModel extends ViewModel { private final SaleRepository saleRepository; @@ -44,12 +47,34 @@ public class SaleListViewModel extends ViewModel { this.customerRepository = customerRepository; } + /** + * Returns the LiveData for the list of sales. + */ public LiveData> getSales() { return sales; } + + /** + * Returns the LiveData for the list of available stores. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the list of available customers. + */ public LiveData> getCustomers() { return customers; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Returns true if the current page is the last page of results. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads a page of sales based on search and filter criteria. + */ public void loadSales(boolean reset, String query, String paymentMethod, Long storeId, Boolean isRefund, Long customerId) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -75,6 +100,9 @@ public class SaleListViewModel extends ViewModel { }); } + /** + * Loads available stores for filtering. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -83,6 +111,9 @@ public class SaleListViewModel extends ViewModel { }); } + /** + * Loads available customers for filtering. + */ public void loadCustomers() { observeOnce(customerRepository.getAllCustomers(0, 500), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -91,6 +122,9 @@ public class SaleListViewModel extends ViewModel { }); } + /** + * Observes a LiveData once, removing the observer after the first non-loading response. + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java index 625abb92..352a08b7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java @@ -26,24 +26,39 @@ public class ServiceDetailViewModel extends ViewModel { this.repository = repository; } + /** + * Sets the service ID and initializes the editing mode. + */ public void setServiceId(long id) { this.serviceId = id; initMode(id != -1); } + /** + * Returns the current service ID. + */ public long getServiceId() { return serviceId; } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { ViewState current = viewState.getValue(); return current != null && current.isEditing; } + /** + * Returns the LiveData for the view state. + */ public LiveData getViewState() { return viewState; } + /** + * Initializes the UI mode (Create vs Edit). + */ public void initMode(boolean isEditing) { updateViewState(state -> { state.isEditing = isEditing; @@ -55,6 +70,9 @@ public class ServiceDetailViewModel extends ViewModel { }); } + /** + * Fetches service details from the repository. + */ public LiveData> loadService() { MutableLiveData> result = new MutableLiveData<>(); observeOnce(repository.getServiceById(serviceId), resource -> { @@ -72,6 +90,9 @@ public class ServiceDetailViewModel extends ViewModel { return result; } + /** + * Saves or updates the service record. + */ public LiveData> saveService(ServiceDTO dto) { updateViewState(state -> { state.serviceName = safeText(dto.getServiceName()); @@ -88,6 +109,9 @@ public class ServiceDetailViewModel extends ViewModel { return repository.createService(dto); } + /** + * Deletes the current service record. + */ public LiveData> deleteService() { return repository.deleteService(serviceId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java index bbdbe270..7e3e9092 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceListViewModel.java @@ -35,10 +35,24 @@ public class ServiceListViewModel extends ViewModel { this.repository = repository; } + /** + * Returns the LiveData for the list of services. + */ public LiveData> getServices() { return services; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of services has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads services from the repository with the specified filters. + */ public void loadServices(boolean reset, String query) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -76,6 +90,9 @@ public class ServiceListViewModel extends ViewModel { }); } + /** + * Deletes multiple services by their IDs. + */ public LiveData> bulkDeleteServices(List ids) { return repository.bulkDeleteServices(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java index 7927465e..8dd538a9 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java @@ -31,31 +31,52 @@ public class StaffDetailViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Loads store dropdown options. + */ public LiveData>> loadStores() { return storeRepository.getStoreDropdowns(); } + /** + * Returns the LiveData for the store list. + */ public LiveData> getStoreList() { return storeList; } + /** + * Updates the store list. + */ public void setStoreList(List list) { storeList.setValue(list); } + /** + * Loads the employee details from the repository. + */ public LiveData> loadEmployee(long id) { return repository.getEmployeeById(id); } + /** + * Sets the current employee ID and editing mode. + */ public void setEmployeeId(long id, boolean isEditing) { this.employeeId = id; this.isEditing = isEditing; } + /** + * Returns the current employee ID. + */ public long getEmployeeId() { return employeeId; } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { return isEditing; } @@ -68,6 +89,9 @@ public class StaffDetailViewModel extends ViewModel { return dto; } + /** + * Saves or updates the employee record. + */ public LiveData> saveEmployee(EmployeeDTO dto) { if (isEditing && employeeId > 0) { return repository.updateEmployee(employeeId, dto); @@ -76,6 +100,9 @@ public class StaffDetailViewModel extends ViewModel { } } + /** + * Deletes the current employee record. + */ public LiveData> deleteEmployee() { return repository.deleteEmployee(employeeId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java index 3dc40816..ed22e3e5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffListViewModel.java @@ -19,6 +19,9 @@ import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; +/** + * ViewModel for managing the list of employees, including filtering and pagination. + */ @HiltViewModel public class StaffListViewModel extends ViewModel { private final EmployeeRepository repository; @@ -43,11 +46,29 @@ public class StaffListViewModel extends ViewModel { this.storeRepository = storeRepository; } + /** + * Returns the LiveData for the list of filtered employees. + */ public LiveData> getFilteredEmployees() { return filteredEmployees; } + + /** + * Returns the LiveData for the list of stores for filtering. + */ public LiveData> getStores() { return stores; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of employees has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads the full list of staff from the repository. + */ public void loadStaff(boolean reset) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -76,6 +97,9 @@ public class StaffListViewModel extends ViewModel { }); } + /** + * Loads the list of stores for filtering options. + */ public void loadStores() { observeOnce(storeRepository.getAllStores(0, 100), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { @@ -84,6 +108,9 @@ public class StaffListViewModel extends ViewModel { }); } + /** + * Observes a LiveData once + */ private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override @@ -96,6 +123,9 @@ public class StaffListViewModel extends ViewModel { }); } + /** + * Filters the employee list locally based on query, store, and status. + */ public void filter(String query, Long storeId, String status) { this.lastQuery = query; this.lastStoreId = storeId; @@ -103,6 +133,9 @@ public class StaffListViewModel extends ViewModel { applyFilter(employees.getValue()); } + /** + * Applies the filters to the full list and updates the filtered LiveData. + */ private void applyFilter(List all) { if (all == null) return; diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java index 54f83ef9..63e03b7d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java @@ -26,24 +26,39 @@ public class SupplierDetailViewModel extends ViewModel { this.repository = repository; } + /** + * Sets the supplier ID and initializes the editing mode. + */ public void setSupId(long id) { this.supId = id; initMode(id != -1); } + /** + * Returns the current supplier ID. + */ public long getSupId() { return supId; } + /** + * Checks if the fragment is in editing mode. + */ public boolean isEditing() { ViewState current = viewState.getValue(); return current != null && current.isEditing; } + /** + * Returns the LiveData for the view state. + */ public LiveData getViewState() { return viewState; } + /** + * Initializes the UI mode (Create vs Edit). + */ public void initMode(boolean isEditing) { updateViewState(state -> { state.isEditing = isEditing; @@ -55,6 +70,9 @@ public class SupplierDetailViewModel extends ViewModel { }); } + /** + * Fetches supplier details from the repository. + */ public LiveData> loadSupplier() { MutableLiveData> result = new MutableLiveData<>(); observeOnce(repository.getSupplierById(supId), resource -> { @@ -73,6 +91,9 @@ public class SupplierDetailViewModel extends ViewModel { return result; } + /** + * Saves or updates the supplier record. + */ public LiveData> saveSupplier(SupplierDTO dto) { if (isEditing()) { dto.setSupId(supId); @@ -81,6 +102,9 @@ public class SupplierDetailViewModel extends ViewModel { return repository.createSupplier(dto); } + /** + * Deletes the current supplier record. + */ public LiveData> deleteSupplier() { return repository.deleteSupplier(supId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java index 33373e90..700977cf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierListViewModel.java @@ -34,10 +34,24 @@ public class SupplierListViewModel extends ViewModel { this.repository = repository; } + /** + * Returns the LiveData for the list of suppliers. + */ public LiveData> getSuppliers() { return suppliers; } + + /** + * Returns the LiveData for the loading state. + */ public LiveData getIsLoading() { return isLoading; } + + /** + * Checks if the last page of suppliers has been reached. + */ public boolean isLastPage() { return isLastPage; } + /** + * Loads suppliers from the repository with the specified filters. + */ public void loadSuppliers(boolean reset, String query) { if (isLoading.getValue() != null && isLoading.getValue() && !reset) return; @@ -77,6 +91,9 @@ public class SupplierListViewModel extends ViewModel { }); } + /** + * Deletes multiple suppliers by their IDs. + */ public LiveData> bulkDeleteSuppliers(List ids) { return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/websocket/StompChatManager.java b/android/app/src/main/java/com/example/petstoremobile/websocket/StompChatManager.java index 3aad4b8d..8d77daef 100644 --- a/android/app/src/main/java/com/example/petstoremobile/websocket/StompChatManager.java +++ b/android/app/src/main/java/com/example/petstoremobile/websocket/StompChatManager.java @@ -18,25 +18,50 @@ import ua.naiksoftware.stomp.Stomp; import ua.naiksoftware.stomp.StompClient; import ua.naiksoftware.stomp.dto.StompHeader; -//Used to handle the websocket connection for the chat +/** + * Manages WebSocket connections and STOMP protocol messaging for the chat system. + */ public class StompChatManager { private static final String TAG = "StompChatManager"; - //Interface for when a message is received + /** + * Interface for receiving new chat messages. + */ public interface MessageListener { + /** + * Called when a new message is received for the current conversation. + */ void onMessageReceived(MessageDTO message); } - //Interface for when a conversation is created or updated + /** + * Interface for receiving updates to the list of conversations. + */ public interface ConversationListener { + /** + * Called when a conversation is created or its details are updated. + */ void onConversationUpdated(ConversationDTO conversation); } - //Interface for when the websocket connection is opened, closed, or has an error + /** + * Interface for monitoring the status of the WebSocket connection. + */ public interface ConnectionListener { + /** + * Called when the connection is successfully established. + */ void onSocketOpened(); + + /** + * Called when the connection is closed. + */ void onSocketClosed(); + + /** + * Called when a connection error occurs. + */ void onSocketError(); } @@ -59,16 +84,25 @@ public class StompChatManager { private boolean manualDisconnect; private Long pendingConversationId; + /** + * Initializes the manager with authentication and server details. + */ public StompChatManager(String authToken, String role, String baseUrl) { this.authToken = authToken; this.role = role == null ? "" : role.trim().toUpperCase(Locale.ROOT); this.baseUrl = baseUrl; } + /** + * Sets the listener for incoming messages. + */ public void setMessageListener(MessageListener listener) { this.messageListener = listener; } + /** + * Sets the listener for conversation list updates. + */ public void setConversationListener(ConversationListener listener) { this.conversationListener = listener; } @@ -77,7 +111,10 @@ public class StompChatManager { this.connectionListener = listener; } - // Set up a stomp connection + /** + * Establishes a connection to the WebSocket server using STOMP. + * Reconnects automatically unless manually disconnected. + */ public void connect() { if (authToken == null || authToken.isBlank()) { Log.e(TAG, "Cannot connect websocket without token"); @@ -143,7 +180,10 @@ public class StompChatManager { )); } - // Subscribes to updates for a specific conversation + /** + * Subscribes to updates for a specific conversation. + * If not connected, the subscription is queued until the connection is established. + */ public void subscribeToConversation(Long conversationId) { pendingConversationId = conversationId; if (!isConnected || stompClient == null) { @@ -154,7 +194,9 @@ public class StompChatManager { subscribeToTopic(conversationId); } - // Clears the current conversation subscription + /** + * Stops listening for messages in the current conversation. + */ public void clearConversationSubscription() { pendingConversationId = null; if (topicDisposable != null && !topicDisposable.isDisposed()) { @@ -163,7 +205,9 @@ public class StompChatManager { } } - //helper function to subscribe to a specific conversation topic + /** + * Subscribes to the specific STOMP topic for a conversation's messages. + */ private void subscribeToTopic(Long conversationId) { if (topicDisposable != null && !topicDisposable.isDisposed()) { topicDisposable.dispose(); @@ -189,7 +233,9 @@ public class StompChatManager { compositeDisposable.add(topicDisposable); } - // Listens for conversation updates and refresh the chat list + /** + * Listens for conversation updates and refresh the chat list + */ private void subscribeToConversationFeeds() { if (conversationsDisposable != null && !conversationsDisposable.isDisposed()) { conversationsDisposable.dispose(); @@ -254,7 +300,9 @@ public class StompChatManager { compositeDisposable.add(errorQueueDisposable); } - // Disconnects the stomp connection + /** + * Disconnects the stomp connection + */ public void disconnect() { manualDisconnect = true; isConnected = false; @@ -267,7 +315,9 @@ public class StompChatManager { } } - // Make the URL for the websocket connection + /** + * Make the URL for the websocket connection + */ private String buildWebSocketUrl() { String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) @@ -281,12 +331,16 @@ public class StompChatManager { return cleanBaseUrl + "/ws/chat"; } - // Helper to check if the current user is a customer + /** + * Helper to check if the current user is a customer + */ private boolean isCustomer() { return "CUSTOMER".equals(role); } - // if connection drops, try to reconnect after 1 second + /** + * if connection drops, try to reconnect after 1 second + */ private void scheduleReconnect() { if (manualDisconnect) { return; -- 2.49.1 From c10c9d6d781f4af9673af4101f1520200fbc76c1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 05:09:57 -0600 Subject: [PATCH 12/42] fix HQL pet query --- .../com/petshop/backend/repository/AppointmentRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 59243df2..b23b1902 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -53,7 +53,7 @@ public interface AppointmentRepository extends JpaRepository List findByPet_Id(Long petId); - @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.pet.petId = :petId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") + @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.pet.id = :petId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") List findByPetIdAndAppointmentDate(@Param("petId") Long petId, @Param("date") LocalDate date); List findByAppointmentDateAndAppointmentStatusIgnoreCase(LocalDate date, String status); -- 2.49.1 From 9d70665b60c6311dc468dad389c00acf05a665ac Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 05:36:52 -0600 Subject: [PATCH 13/42] clean flyway config --- backend/src/main/resources/application.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 21d7f6d4..20773f86 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -40,7 +40,6 @@ spring: flyway: enabled: ${FLYWAY_ENABLED:false} - validate-on-migrate: false server: port: ${SERVER_PORT:8080} -- 2.49.1 From b223b214710b69a0d673d89aca7b7ee47b32c6f8 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 05:45:10 -0600 Subject: [PATCH 14/42] add flyway baseline config --- backend/src/main/resources/application.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 20773f86..89ad051f 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -40,6 +40,8 @@ spring: flyway: enabled: ${FLYWAY_ENABLED:false} + baseline-on-migrate: true + baseline-version: 0 server: port: ${SERVER_PORT:8080} -- 2.49.1 From bfaa1b0f7ba04158e096b0cf5156fa97acf0fe3c Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 05:53:49 -0600 Subject: [PATCH 15/42] fix flyway baseline config --- .../com/petshop/backend/config/FlywayContextInitializer.java | 3 ++- backend/src/main/resources/application.yml | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java index d7846581..15c9b976 100644 --- a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java @@ -37,7 +37,8 @@ public class FlywayContextInitializer implements ApplicationContextInitializer Date: Mon, 20 Apr 2026 06:02:05 -0600 Subject: [PATCH 16/42] idempotent schema indexes --- .../db/migration/V1__target_baseline.sql | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql index 6f03cf58..41210e0a 100644 --- a/backend/src/main/resources/db/migration/V1__target_baseline.sql +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -27,7 +27,10 @@ CREATE TABLE IF NOT EXISTS users ( tokenVersion INT NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_users_primary_store FOREIGN KEY (primaryStoreId) REFERENCES storeLocation(storeId) ON DELETE SET NULL + CONSTRAINT fk_users_primary_store FOREIGN KEY (primaryStoreId) REFERENCES storeLocation(storeId) ON DELETE SET NULL, + INDEX idx_users_primary_store (primaryStoreId), + INDEX idx_users_role (role), + INDEX idx_users_name (lastName, firstName) ); CREATE TABLE IF NOT EXISTS supplier ( @@ -65,7 +68,8 @@ CREATE TABLE IF NOT EXISTS service_species ( species VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (serviceId, species), - CONSTRAINT fk_service_species_service FOREIGN KEY (serviceId) REFERENCES service(serviceId) ON DELETE CASCADE + CONSTRAINT fk_service_species_service FOREIGN KEY (serviceId) REFERENCES service(serviceId) ON DELETE CASCADE, + INDEX idx_service_species_species (species) ); CREATE TABLE IF NOT EXISTS product ( @@ -89,7 +93,9 @@ CREATE TABLE IF NOT EXISTS inventory ( updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT uq_inventory_store_product UNIQUE (storeId, prodId), CONSTRAINT fk_inventory_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - CONSTRAINT fk_inventory_product FOREIGN KEY (prodId) REFERENCES product(prodId) + CONSTRAINT fk_inventory_product FOREIGN KEY (prodId) REFERENCES product(prodId), + INDEX idx_inventory_store (storeId), + INDEX idx_inventory_product (prodId) ); CREATE TABLE IF NOT EXISTS productSupplier ( @@ -111,7 +117,8 @@ CREATE TABLE IF NOT EXISTS purchaseOrder ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_purchase_order_supplier FOREIGN KEY (supId) REFERENCES supplier(supId), - CONSTRAINT fk_purchase_order_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) + CONSTRAINT fk_purchase_order_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + INDEX idx_purchase_order_store (storeId) ); CREATE TABLE IF NOT EXISTS coupon ( @@ -143,7 +150,11 @@ CREATE TABLE IF NOT EXISTS pet ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_pet_owner_user FOREIGN KEY (ownerUserId) REFERENCES users(id) ON DELETE SET NULL, - CONSTRAINT fk_pet_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ON DELETE SET NULL + CONSTRAINT fk_pet_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ON DELETE SET NULL, + INDEX idx_pet_owner_user (ownerUserId), + INDEX idx_pet_store (storeId), + INDEX idx_pet_species (petSpecies), + INDEX idx_pet_name (petName) ); CREATE TABLE IF NOT EXISTS appointment ( @@ -162,7 +173,12 @@ CREATE TABLE IF NOT EXISTS appointment ( CONSTRAINT fk_appointment_pet FOREIGN KEY (petId) REFERENCES pet(petId) ON DELETE SET NULL, CONSTRAINT fk_appointment_customer FOREIGN KEY (customerId) REFERENCES users(id), CONSTRAINT fk_appointment_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - CONSTRAINT fk_appointment_employee FOREIGN KEY (employeeId) REFERENCES users(id) + CONSTRAINT fk_appointment_employee FOREIGN KEY (employeeId) REFERENCES users(id), + INDEX idx_appointment_store (storeId), + INDEX idx_appointment_employee (employeeId), + INDEX idx_appointment_customer (customerId), + INDEX idx_appointment_pet (petId), + INDEX idx_appointment_date_status (appointmentDate, appointmentStatus) ); CREATE TABLE IF NOT EXISTS adoption ( @@ -178,7 +194,9 @@ CREATE TABLE IF NOT EXISTS adoption ( CONSTRAINT fk_adoption_pet FOREIGN KEY (petId) REFERENCES pet(petId), CONSTRAINT fk_adoption_customer FOREIGN KEY (customerId) REFERENCES users(id), CONSTRAINT fk_adoption_employee FOREIGN KEY (employeeId) REFERENCES users(id), - CONSTRAINT fk_adoption_source_store FOREIGN KEY (sourceStoreId) REFERENCES storeLocation(storeId) + CONSTRAINT fk_adoption_source_store FOREIGN KEY (sourceStoreId) REFERENCES storeLocation(storeId), + INDEX idx_adoption_store (sourceStoreId), + INDEX idx_adoption_employee (employeeId) ); CREATE TABLE IF NOT EXISTS cart ( @@ -200,7 +218,8 @@ CREATE TABLE IF NOT EXISTS cart ( updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_cart_user FOREIGN KEY (userId) REFERENCES users(id), CONSTRAINT fk_cart_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ON DELETE SET NULL, - CONSTRAINT fk_cart_coupon FOREIGN KEY (couponId) REFERENCES coupon(couponId) ON DELETE SET NULL + CONSTRAINT fk_cart_coupon FOREIGN KEY (couponId) REFERENCES coupon(couponId) ON DELETE SET NULL, + INDEX idx_cart_user (userId) ); CREATE TABLE IF NOT EXISTS cart_item ( @@ -243,7 +262,11 @@ CREATE TABLE IF NOT EXISTS sale ( CONSTRAINT fk_sale_customer FOREIGN KEY (customerId) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_sale_original_sale FOREIGN KEY (originalSaleId) REFERENCES sale(saleId), CONSTRAINT fk_sale_cart FOREIGN KEY (cartId) REFERENCES cart(cartId) ON DELETE SET NULL, - CONSTRAINT fk_sale_coupon FOREIGN KEY (couponId) REFERENCES coupon(couponId) ON DELETE SET NULL + CONSTRAINT fk_sale_coupon FOREIGN KEY (couponId) REFERENCES coupon(couponId) ON DELETE SET NULL, + INDEX idx_sale_store (storeId), + INDEX idx_sale_employee (employeeId), + INDEX idx_sale_customer (customerId), + INDEX idx_sale_date (saleDate) ); CREATE TABLE IF NOT EXISTS saleItem ( @@ -291,12 +314,11 @@ CREATE TABLE IF NOT EXISTS passwordResetToken ( usedAt DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_password_reset_token_hash UNIQUE (tokenHash), - CONSTRAINT fk_password_reset_token_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE + CONSTRAINT fk_password_reset_token_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE, + INDEX idx_password_reset_token_user (userId), + INDEX idx_password_reset_token_expires (expiresAt) ); -CREATE INDEX idx_password_reset_token_user ON passwordResetToken(userId); -CREATE INDEX idx_password_reset_token_expires ON passwordResetToken(expiresAt); - CREATE TABLE IF NOT EXISTS conversation ( id BIGINT AUTO_INCREMENT PRIMARY KEY, customerId BIGINT NOT NULL, @@ -307,7 +329,9 @@ CREATE TABLE IF NOT EXISTS conversation ( created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_conversation_customer FOREIGN KEY (customerId) REFERENCES users(id), - CONSTRAINT fk_conversation_staff FOREIGN KEY (staffId) REFERENCES users(id) ON DELETE SET NULL + CONSTRAINT fk_conversation_staff FOREIGN KEY (staffId) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_conversation_customer (customerId), + INDEX idx_conversation_staff (staffId) ); CREATE TABLE IF NOT EXISTS message ( @@ -336,33 +360,8 @@ CREATE TABLE IF NOT EXISTS activityLog ( activity TEXT NOT NULL, logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_activity_log_user FOREIGN KEY (userId) REFERENCES users(id), - CONSTRAINT fk_activity_log_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ON DELETE SET NULL + CONSTRAINT fk_activity_log_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ON DELETE SET NULL, + INDEX idx_activity_log_store (storeId), + INDEX idx_activity_log_timestamp_id (logTimestamp, logId) ); -CREATE INDEX idx_users_primary_store ON users(primaryStoreId); -CREATE INDEX idx_users_role ON users(role); -CREATE INDEX idx_users_name ON users(lastName, firstName); -CREATE INDEX idx_service_species_species ON service_species(species); -CREATE INDEX idx_inventory_store ON inventory(storeId); -CREATE INDEX idx_inventory_product ON inventory(prodId); -CREATE INDEX idx_purchase_order_store ON purchaseOrder(storeId); -CREATE INDEX idx_pet_owner_user ON pet(ownerUserId); -CREATE INDEX idx_pet_store ON pet(storeId); -CREATE INDEX idx_pet_species ON pet(petSpecies); -CREATE INDEX idx_pet_name ON pet(petName); -CREATE INDEX idx_appointment_store ON appointment(storeId); -CREATE INDEX idx_appointment_employee ON appointment(employeeId); -CREATE INDEX idx_appointment_customer ON appointment(customerId); -CREATE INDEX idx_appointment_pet ON appointment(petId); -CREATE INDEX idx_appointment_date_status ON appointment(appointmentDate, appointmentStatus); -CREATE INDEX idx_adoption_store ON adoption(sourceStoreId); -CREATE INDEX idx_adoption_employee ON adoption(employeeId); -CREATE INDEX idx_sale_store ON sale(storeId); -CREATE INDEX idx_sale_employee ON sale(employeeId); -CREATE INDEX idx_sale_customer ON sale(customerId); -CREATE INDEX idx_sale_date ON sale(saleDate); -CREATE INDEX idx_cart_user ON cart(userId); -CREATE INDEX idx_conversation_customer ON conversation(customerId); -CREATE INDEX idx_conversation_staff ON conversation(staffId); -CREATE INDEX idx_activity_log_store ON activityLog(storeId); -CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId); -- 2.49.1 From fc3f1eb6bea052f6c7816db2f79b24374c9c564b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 06:16:56 -0600 Subject: [PATCH 17/42] drop status from seed data --- .../resources/db/migration/V2__seed_data.sql | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 33505a84..4109ec6a 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -791,43 +791,43 @@ INSERT INTO inventory (inventoryId, storeId, prodId, quantity) VALUES (299, 3, 99, 57), (300, 3, 100, 64); -INSERT INTO purchaseOrder (purchaseOrderId, supId, storeId, orderDate, status) VALUES -(1, 3, 1, '2026-01-06', 'RECEIVED'), -(2, 4, 1, '2026-01-13', 'RECEIVED'), -(3, 5, 1, '2026-01-20', 'RECEIVED'), -(4, 4, 2, '2026-01-07', 'RECEIVED'), -(5, 5, 2, '2026-01-14', 'RECEIVED'), -(6, 6, 2, '2026-01-21', 'RECEIVED'), -(7, 5, 3, '2026-01-08', 'RECEIVED'), -(8, 6, 3, '2026-01-15', 'RECEIVED'), -(9, 7, 3, '2026-01-22', 'RECEIVED'), -(10, 4, 1, '2026-02-06', 'RECEIVED'), -(11, 5, 1, '2026-02-13', 'RECEIVED'), -(12, 6, 1, '2026-02-20', 'RECEIVED'), -(13, 5, 2, '2026-02-07', 'RECEIVED'), -(14, 6, 2, '2026-02-14', 'RECEIVED'), -(15, 7, 2, '2026-02-21', 'RECEIVED'), -(16, 6, 3, '2026-02-08', 'RECEIVED'), -(17, 7, 3, '2026-02-15', 'RECEIVED'), -(18, 8, 3, '2026-02-22', 'RECEIVED'), -(19, 5, 1, '2026-03-06', 'RECEIVED'), -(20, 6, 1, '2026-03-13', 'RECEIVED'), -(21, 7, 1, '2026-03-20', 'RECEIVED'), -(22, 6, 2, '2026-03-07', 'RECEIVED'), -(23, 7, 2, '2026-03-14', 'RECEIVED'), -(24, 8, 2, '2026-03-21', 'RECEIVED'), -(25, 7, 3, '2026-03-08', 'RECEIVED'), -(26, 8, 3, '2026-03-15', 'RECEIVED'), -(27, 9, 3, '2026-03-22', 'RECEIVED'), -(28, 6, 1, '2026-04-06', 'PENDING'), -(29, 7, 1, '2026-04-13', 'RECEIVED'), -(30, 8, 1, '2026-04-20', 'PLACED'), -(31, 7, 2, '2026-04-07', 'RECEIVED'), -(32, 8, 2, '2026-04-14', 'PLACED'), -(33, 9, 2, '2026-04-21', 'PENDING'), -(34, 8, 3, '2026-04-08', 'PLACED'), -(35, 9, 3, '2026-04-15', 'PENDING'), -(36, 10, 3, '2026-04-22', 'RECEIVED'); +INSERT INTO purchaseOrder (purchaseOrderId, supId, storeId, orderDate) VALUES +(1, 3, 1, '2026-01-06'), +(2, 4, 1, '2026-01-13'), +(3, 5, 1, '2026-01-20'), +(4, 4, 2, '2026-01-07'), +(5, 5, 2, '2026-01-14'), +(6, 6, 2, '2026-01-21'), +(7, 5, 3, '2026-01-08'), +(8, 6, 3, '2026-01-15'), +(9, 7, 3, '2026-01-22'), +(10, 4, 1, '2026-02-06'), +(11, 5, 1, '2026-02-13'), +(12, 6, 1, '2026-02-20'), +(13, 5, 2, '2026-02-07'), +(14, 6, 2, '2026-02-14'), +(15, 7, 2, '2026-02-21'), +(16, 6, 3, '2026-02-08'), +(17, 7, 3, '2026-02-15'), +(18, 8, 3, '2026-02-22'), +(19, 5, 1, '2026-03-06'), +(20, 6, 1, '2026-03-13'), +(21, 7, 1, '2026-03-20'), +(22, 6, 2, '2026-03-07'), +(23, 7, 2, '2026-03-14'), +(24, 8, 2, '2026-03-21'), +(25, 7, 3, '2026-03-08'), +(26, 8, 3, '2026-03-15'), +(27, 9, 3, '2026-03-22'), +(28, 6, 1, '2026-04-06'), +(29, 7, 1, '2026-04-13'), +(30, 8, 1, '2026-04-20'), +(31, 7, 2, '2026-04-07'), +(32, 8, 2, '2026-04-14'), +(33, 9, 2, '2026-04-21'), +(34, 8, 3, '2026-04-08'), +(35, 9, 3, '2026-04-15'), +(36, 10, 3, '2026-04-22'); INSERT INTO coupon (couponId, couponCode, discountType, discountValue, minOrderAmount, active, startsAt, endsAt, usageLimit) VALUES (1, 'NOCODE', 'FIXED', 0.00, 0.00, 1, NULL, NULL, NULL), -- 2.49.1 From f5d90c1d4f8f9bb22c9c149b02e1a2ac5258eded Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 06:28:08 -0600 Subject: [PATCH 18/42] fix store imageUrl length --- backend/src/main/resources/db/migration/V1__target_baseline.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql index 41210e0a..01a0c34e 100644 --- a/backend/src/main/resources/db/migration/V1__target_baseline.sql +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS storeLocation ( address VARCHAR(255) NOT NULL, phone VARCHAR(20) NOT NULL, email VARCHAR(100) NOT NULL, - imageUrl VARCHAR(255) NULL, + imageUrl VARCHAR(500) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 2.49.1 From aa48d2428df092db72ca0be6b91a0571d296ea29 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 07:07:21 -0600 Subject: [PATCH 19/42] Add full QA script --- test-backend-full.sh | 725 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 725 insertions(+) create mode 100755 test-backend-full.sh diff --git a/test-backend-full.sh b/test-backend-full.sh new file mode 100755 index 00000000..ad8cbf75 --- /dev/null +++ b/test-backend-full.sh @@ -0,0 +1,725 @@ +#!/usr/bin/env bash +set -uo pipefail + +BASE="https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io/api/v1" +PASS=0 FAIL=0 + +CLEANUP_USER_IDS=() +CLEANUP_PRODUCT_IDS=() +CLEANUP_CATEGORY_IDS=() +CLEANUP_PET_IDS=() +CLEANUP_MYPET_IDS=() +CLEANUP_SERVICE_IDS=() +CLEANUP_STORE_IDS=() +CLEANUP_APPT_IDS=() +CLEANUP_ADOPTION_IDS=() +CLEANUP_SALE_IDS=() +CLEANUP_COUPON_IDS=() +CLEANUP_SUPPLIER_IDS=() +CLEANUP_CONV_IDS=() + +check() { + local label="$1" expect="$2" method="$3" path="$4"; shift 4 + local code + code=$(curl -s -o /tmp/qa_body.json -w "%{http_code}" -X "$method" "$BASE$path" "$@" 2>/dev/null) + if [ "$code" = "$expect" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — expected $expect got $code"; fi +} + +check_field() { + local label="$1" expr="$2" expected="$3" + local actual + actual=$(jq -r "$expr" /tmp/qa_body.json 2>/dev/null) + if [ "$actual" = "$expected" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — expected '$expected' got '$actual'"; fi +} + +check_field_contains() { + local label="$1" expr="$2" expected="$3" + local actual + actual=$(jq -r "$expr" /tmp/qa_body.json 2>/dev/null) + if echo "$actual" | grep -q "$expected" 2>/dev/null; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — expected contains '$expected' got '$actual'"; fi +} + +check_field_gt() { + local label="$1" expr="$2" threshold="$3" + local actual + actual=$(jq -r "$expr" /tmp/qa_body.json 2>/dev/null) + if [ "$(echo "$actual > $threshold" | bc 2>/dev/null)" = "1" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — expected > $threshold got '$actual'"; fi +} + +id_from_body() { jq -r '.id // .userId // empty' /tmp/qa_body.json 2>/dev/null; } + +echo "=========================================" +echo " PET SHOP QA — Full Backend Test Suite" +echo "=========================================" + +echo "--- 1. AUTH ---" + +check "Admin login" 200 POST "/auth/login" -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' +ADMIN_TOKEN=$(jq -r '.token' /tmp/qa_body.json) +if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then echo "FATAL: Admin login failed"; exit 1; fi + +check "Staff login" 200 POST "/auth/login" -H 'Content-Type: application/json' -d '{"username":"staff","password":"staff123"}' +STAFF_TOKEN=$(jq -r '.token' /tmp/qa_body.json) +if [ "$STAFF_TOKEN" = "null" ] || [ -z "$STAFF_TOKEN" ]; then echo "FATAL: Staff login failed"; exit 1; fi + +check "Customer login" 200 POST "/auth/login" -H 'Content-Type: application/json' -d '{"username":"customer","password":"customer123"}' +CUST_TOKEN=$(jq -r '.token' /tmp/qa_body.json) +if [ "$CUST_TOKEN" = "null" ] || [ -z "$CUST_TOKEN" ]; then echo "FATAL: Customer login failed"; exit 1; fi + +A=(-H "Authorization: Bearer $ADMIN_TOKEN") +S=(-H "Authorization: Bearer $STAFF_TOKEN") +C=(-H "Authorization: Bearer $CUST_TOKEN") +J=(-H "Content-Type: application/json") + +check "GET /auth/me admin" 200 GET "/auth/me" "${A[@]}" +check_field "Admin role" ".role" "ADMIN" +check "GET /auth/me staff" 200 GET "/auth/me" "${S[@]}" +check_field "Staff role" ".role" "STAFF" +check "GET /auth/me customer" 200 GET "/auth/me" "${C[@]}" +check_field "Customer role" ".role" "CUSTOMER" + +check "Admin avatar file" 200 GET "/auth/me/avatar/file" "${A[@]}" +check "Wrong password" 401 POST "/auth/login" "${J[@]}" -d '{"username":"admin","password":"wrong"}' +check "Empty login body" 400 POST "/auth/login" "${J[@]}" -d '{}' + +check "Register QA user" 201 POST "/auth/register" "${J[@]}" -d '{"username":"testuser_qa","password":"Test1234!","email":"qa@test.com","firstName":"QA","lastName":"Test","phone":"5551234567"}' +QA_USER_ID=$(id_from_body) +[ -n "$QA_USER_ID" ] && CLEANUP_USER_IDS+=("$QA_USER_ID") + +check "Login QA user" 200 POST "/auth/login" "${J[@]}" -d '{"username":"testuser_qa","password":"Test1234!"}' +QA_TOKEN=$(jq -r '.token' /tmp/qa_body.json) +check "GET /me QA user" 200 GET "/auth/me" -H "Authorization: Bearer $QA_TOKEN" +check_field "QA username" ".username" "testuser_qa" + +check "Duplicate register" 409 POST "/auth/register" "${J[@]}" -d '{"username":"admin","password":"Test1234!","email":"dup@test.com","firstName":"A","lastName":"B","phone":"5550000000"}' +if [ "$(jq -r '.status // empty' /tmp/qa_body.json 2>/dev/null)" != "" ]; then PASS=$((PASS)); else + check "Duplicate register alt" 400 POST "/auth/register" "${J[@]}" -d '{"username":"admin","password":"Test1234!","email":"dup2@test.com","firstName":"A","lastName":"B","phone":"5550000001"}' +fi +check "Register missing fields" 400 POST "/auth/register" "${J[@]}" -d '{"username":"incomplete_qa"}' + +echo "--- 2. PRODUCTS ---" + +check "GET /products" 200 GET "/products" +check "GET /products/1" 200 GET "/products/1" +check "GET /products?q=dog" 200 GET "/products?q=dog" +check "GET /products?categoryId=1" 200 GET "/products?categoryId=1" +check "GET /products/999999" 404 GET "/products/999999" + +check "Create product (admin)" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Product","description":"QA test","price":9.99,"categoryId":1}' +PROD_ID=$(id_from_body) +[ -n "$PROD_ID" ] && CLEANUP_PRODUCT_IDS+=("$PROD_ID") +check_field "Product name" ".prodName" "QA_TEST_Product" + +check "Create product (customer)" 403 POST "/products" "${C[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Nope","price":1,"categoryId":1}' +check "Create product (no auth)" 401 POST "/products" "${J[@]}" -d '{"prodName":"QA_TEST_Nope2","price":1,"categoryId":1}' +check "Create product missing name" 400 POST "/products" "${A[@]}" "${J[@]}" -d '{"price":1,"categoryId":1}' + +if [ -n "$PROD_ID" ]; then + check "Update product" 200 PUT "/products/$PROD_ID" "${A[@]}" "${J[@]}" -d "{\"prodName\":\"QA_TEST_Updated\",\"description\":\"updated\",\"price\":19.99,\"categoryId\":1}" + check_field "Updated name" ".prodName" "QA_TEST_Updated" +fi + +check "Create product to delete" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_DeleteMe","description":"del","price":1,"categoryId":1}' +DEL_PROD_ID=$(id_from_body) +if [ -n "$DEL_PROD_ID" ]; then + check "Delete product" 200 DELETE "/products/$DEL_PROD_ID" "${A[@]}" + check "GET deleted product" 404 GET "/products/$DEL_PROD_ID" +fi + +check "GET /products paginated" 200 GET "/products?page=0&size=5" +check "GET /products sorted" 200 GET "/products?sort=price,desc" + +echo "--- 3. CATEGORIES ---" + +check "GET /categories" 200 GET "/categories" +check "GET /categories/1" 200 GET "/categories/1" +check "GET /categories/999999" 404 GET "/categories/999999" + +check "Create category (admin)" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"catName":"QA_TEST_Category","description":"qa"}' +CAT_ID=$(id_from_body) +[ -n "$CAT_ID" ] && CLEANUP_CATEGORY_IDS+=("$CAT_ID") + +check "Create category (customer)" 403 POST "/categories" "${C[@]}" "${J[@]}" -d '{"catName":"QA_TEST_Nope"}' +check "Create category missing name" 400 POST "/categories" "${A[@]}" "${J[@]}" -d '{"description":"no name"}' + +if [ -n "$CAT_ID" ]; then + check "Update category" 200 PUT "/categories/$CAT_ID" "${A[@]}" "${J[@]}" -d "{\"catName\":\"QA_TEST_CatUpdated\",\"description\":\"updated\"}" + check_field "Updated cat name" ".catName" "QA_TEST_CatUpdated" +fi + +check "Create category to delete" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"catName":"QA_TEST_CatDel","description":"del"}' +DEL_CAT_ID=$(id_from_body) +if [ -n "$DEL_CAT_ID" ]; then + check "Delete category" 200 DELETE "/categories/$DEL_CAT_ID" "${A[@]}" + check "GET deleted category" 404 GET "/categories/$DEL_CAT_ID" +fi + +echo "--- 4. PETS ---" + +check "GET /pets" 200 GET "/pets" +check "GET /pets/2" 200 GET "/pets/2" +check "GET /pets?species=Dog" 200 GET "/pets?species=Dog" +check "GET /pets?status=Available" 200 GET "/pets?status=Available" +check "GET /pets/999999" 404 GET "/pets/999999" +check "GET /my-pets (customer)" 200 GET "/my-pets" "${C[@]}" + +check "Admin create pet" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"name":"QA_TEST_Pet","species":"Dog","breed":"Labrador","age":2,"gender":"Male","status":"Available","storeId":1,"price":100,"description":"QA test pet"}' +PET_ID=$(id_from_body) +[ -n "$PET_ID" ] && CLEANUP_PET_IDS+=("$PET_ID") +check_field "Pet name" ".name" "QA_TEST_Pet" +check_field "Pet species" ".species" "Dog" + +if [ -n "$PET_ID" ]; then + check "Update pet" 200 PUT "/pets/$PET_ID" "${A[@]}" "${J[@]}" -d "{\"name\":\"QA_TEST_PetUpd\",\"species\":\"Dog\",\"breed\":\"Labrador\",\"age\":3,\"gender\":\"Male\",\"status\":\"Available\",\"storeId\":1,\"price\":150,\"description\":\"updated\"}" + check_field "Updated pet name" ".name" "QA_TEST_PetUpd" +fi + +check "Admin create pet to delete" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"name":"QA_TEST_PetDel","species":"Cat","breed":"Siamese","age":1,"gender":"Female","status":"Available","storeId":1,"price":50,"description":"del"}' +DEL_PET_ID=$(id_from_body) +if [ -n "$DEL_PET_ID" ]; then + check "Delete pet" 200 DELETE "/pets/$DEL_PET_ID" "${A[@]}" + check "GET deleted pet" 404 GET "/pets/$DEL_PET_ID" +fi + +check "Customer create my-pet" 201 POST "/my-pets" "${C[@]}" "${J[@]}" -d '{"name":"QA_TEST_MyPet","species":"Rabbit","breed":"Holland Lop","age":1,"gender":"Female","description":"my pet"}' +MY_PET_ID=$(id_from_body) +[ -n "$MY_PET_ID" ] && CLEANUP_MYPET_IDS+=("$MY_PET_ID") +check "GET /my-pets has new pet" 200 GET "/my-pets" "${C[@]}" + +check "Create pet (customer direct)" 403 POST "/pets" "${C[@]}" "${J[@]}" -d '{"name":"QA_TEST_Nope","species":"Dog","breed":"Lab","age":1,"gender":"Male","status":"Available","storeId":1,"price":10}' +check "Create pet (no auth)" 401 POST "/pets" "${J[@]}" -d '{"name":"QA_TEST_Nope2","species":"Dog","breed":"Lab","age":1,"gender":"Male","status":"Available","storeId":1,"price":10}' +check "GET /pets paginated" 200 GET "/pets?page=0&size=5" + +echo "--- 5. SERVICES ---" + +check "GET /services" 200 GET "/services" +check "GET /services/1" 200 GET "/services/1" +check "GET /services?species=Dog" 200 GET "/services?species=Dog" +check "GET /services/999999" 404 GET "/services/999999" + +check "Create service (admin)" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Service","description":"qa svc","basePrice":25.00,"duration":30,"species":["Dog","Cat"]}' +SVC_ID=$(id_from_body) +[ -n "$SVC_ID" ] && CLEANUP_SERVICE_IDS+=("$SVC_ID") +check_field "Service name" ".serviceName" "QA_TEST_Service" + +if [ -n "$SVC_ID" ]; then + check "Update service" 200 PUT "/services/$SVC_ID" "${A[@]}" "${J[@]}" -d "{\"serviceName\":\"QA_TEST_SvcUpd\",\"description\":\"upd\",\"basePrice\":30,\"duration\":45,\"species\":[\"Dog\"]}" +fi + +check "Create service to delete" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_SvcDel","description":"del","basePrice":10,"duration":15,"species":["Cat"]}' +DEL_SVC_ID=$(id_from_body) +if [ -n "$DEL_SVC_ID" ]; then + check "Delete service" 200 DELETE "/services/$DEL_SVC_ID" "${A[@]}" +fi +check "Create service (customer)" 403 POST "/services" "${C[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Nope","basePrice":10,"duration":10,"species":["Dog"]}' + +echo "--- 6. STORES ---" + +check "GET /stores" 200 GET "/stores" +check "GET /stores/1" 200 GET "/stores/1" +check "GET /stores/999999" 404 GET "/stores/999999" + +check "Create store (admin)" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Store","address":"123 QA St","city":"Testville","state":"QA","zipCode":"00000","phone":"5559999999"}' +STORE_ID=$(id_from_body) +[ -n "$STORE_ID" ] && CLEANUP_STORE_IDS+=("$STORE_ID") + +if [ -n "$STORE_ID" ]; then + check "Update store" 200 PUT "/stores/$STORE_ID" "${A[@]}" "${J[@]}" -d "{\"storeName\":\"QA_TEST_StoreUpd\",\"address\":\"456 QA Ave\",\"city\":\"Testville\",\"state\":\"QA\",\"zipCode\":\"00001\",\"phone\":\"5559999998\"}" +fi + +check "Create store to delete" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_StoreDel","address":"789 Del Rd","city":"Gone","state":"QA","zipCode":"00002","phone":"5559999997"}' +DEL_STORE_ID=$(id_from_body) +if [ -n "$DEL_STORE_ID" ]; then + check "Delete store" 200 DELETE "/stores/$DEL_STORE_ID" "${A[@]}" +fi + +check "Create store (staff)" 403 POST "/stores" "${S[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Nope","address":"x","city":"x","state":"x","zipCode":"x","phone":"x"}' + +echo "--- 7. USERS / EMPLOYEES / CUSTOMERS ---" + +check "GET /users (admin)" 200 GET "/users" "${A[@]}" +check "GET /users (customer)" 403 GET "/users" "${C[@]}" +check "GET /users/1" 200 GET "/users/1" "${A[@]}" +check "GET /users/999999" 404 GET "/users/999999" "${A[@]}" + +check "GET /employees (admin)" 200 GET "/employees" "${A[@]}" +check "GET /employees (staff)" 403 GET "/employees" "${S[@]}" +check "GET /customers (staff)" 200 GET "/customers" "${S[@]}" +check "GET /customers (customer)" 403 GET "/customers" "${C[@]}" + +check "Admin create user" 201 POST "/users" "${A[@]}" "${J[@]}" -d '{"username":"qa_test_user2","password":"Test1234!","email":"qa2@test.com","firstName":"QA2","lastName":"Test2","phone":"5552222222","role":"CUSTOMER"}' +NEW_USER_ID=$(id_from_body) +[ -n "$NEW_USER_ID" ] && CLEANUP_USER_IDS+=("$NEW_USER_ID") +check_field "Created username" ".username" "qa_test_user2" + +if [ -n "$NEW_USER_ID" ]; then + check "Update user" 200 PUT "/users/$NEW_USER_ID" "${A[@]}" "${J[@]}" -d "{\"username\":\"qa_test_user2\",\"email\":\"qa2upd@test.com\",\"firstName\":\"QA2U\",\"lastName\":\"Test2U\",\"phone\":\"5552222223\",\"role\":\"CUSTOMER\"}" +fi + +check "GET /users/1/avatar/file" 200 GET "/users/1/avatar/file" "${A[@]}" +check "GET /users/50/avatar/file (default)" 200 GET "/users/50/avatar/file" "${A[@]}" + +check "GET /users no auth" 401 GET "/users" +check "Create user (staff)" 403 POST "/users" "${S[@]}" "${J[@]}" -d '{"username":"qa_nope","password":"x","email":"n@n.com","firstName":"N","lastName":"N","phone":"0","role":"CUSTOMER"}' +check "Create user (customer)" 403 POST "/users" "${C[@]}" "${J[@]}" -d '{"username":"qa_nope2","password":"x","email":"n2@n.com","firstName":"N","lastName":"N","phone":"0","role":"CUSTOMER"}' + +echo "--- 8. APPOINTMENTS ---" + +check "GET /appointments (admin)" 200 GET "/appointments" "${A[@]}" +check "GET /appointments/1" 200 GET "/appointments/1" "${A[@]}" +check "GET /appointments?storeId=1" 200 GET "/appointments?storeId=1" "${A[@]}" +check "GET /appointments?status=Scheduled" 200 GET "/appointments?status=Scheduled" "${A[@]}" +check "GET /appointments/availability" 200 GET "/appointments/availability?storeId=1&serviceId=1&date=2027-06-01" "${A[@]}" +check "GET /appointments/999999" 404 GET "/appointments/999999" "${A[@]}" + +check "Create appointment" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-06-15","appointmentTime":"10:00","notes":"QA_TEST"}' +APPT_ID=$(id_from_body) +[ -n "$APPT_ID" ] && CLEANUP_APPT_IDS+=("$APPT_ID") +check_field "Appointment status" ".status" "Scheduled" + +if [ -n "$APPT_ID" ]; then + check "Cancel appointment" 200 PUT "/appointments/$APPT_ID" "${A[@]}" "${J[@]}" -d "{\"petId\":53,\"customerId\":32,\"storeId\":2,\"employeeId\":7,\"serviceId\":1,\"appointmentDate\":\"2027-06-15\",\"appointmentTime\":\"10:00\",\"status\":\"Cancelled\",\"notes\":\"QA_TEST cancelled\"}" + check_field "Cancelled status" ".status" "Cancelled" +fi + +check "Create appointment to delete" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-07-01","appointmentTime":"14:00","notes":"QA_TEST_DEL"}' +DEL_APPT_ID=$(id_from_body) +if [ -n "$DEL_APPT_ID" ]; then + check "Delete appointment" 200 DELETE "/appointments/$DEL_APPT_ID" "${A[@]}" +fi + +check "Appointment past date" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2020-01-01","appointmentTime":"10:00","notes":"QA_TEST past"}' + +check "Pet-service mismatch" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":38,"customerId":17,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-08-01","appointmentTime":"10:00","notes":"QA_TEST mismatch"}' + +check "Create appt for duplicate test" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","notes":"QA_TEST dup1"}' +DUP_APPT_ID=$(id_from_body) +[ -n "$DUP_APPT_ID" ] && CLEANUP_APPT_IDS+=("$DUP_APPT_ID") +check "Duplicate time/employee" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","notes":"QA_TEST dup2"}' + +check "GET /appointments (customer)" 200 GET "/appointments" "${C[@]}" +check "GET /appointments (staff)" 200 GET "/appointments" "${S[@]}" +check "Create appointment (no auth)" 401 POST "/appointments" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-10-01","appointmentTime":"10:00"}' + +echo "--- 9. ADOPTIONS ---" + +check "GET /adoptions (admin)" 200 GET "/adoptions" "${A[@]}" +check "GET /adoptions/1" 200 GET "/adoptions/1" "${A[@]}" +check "GET /adoptions/999999" 404 GET "/adoptions/999999" "${A[@]}" + +AVAIL_PET_1="" +AVAIL_PET_2="" +AVAIL_PET_3="" +curl -s "$BASE/pets?status=Available&size=5" "${A[@]}" -o /tmp/qa_avail.json 2>/dev/null +AVAIL_PET_1=$(jq -r '.content[0].id // empty' /tmp/qa_avail.json 2>/dev/null) +AVAIL_PET_2=$(jq -r '.content[1].id // empty' /tmp/qa_avail.json 2>/dev/null) +AVAIL_PET_3=$(jq -r '.content[2].id // empty' /tmp/qa_avail.json 2>/dev/null) + +if [ -n "$AVAIL_PET_1" ]; then + check "Staff create adoption (Pending)" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST adopt1\"}" + ADOPT_ID_1=$(id_from_body) + [ -n "$ADOPT_ID_1" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_1") + + check "Pet now Pending" 200 GET "/pets/$AVAIL_PET_1" + check_field "Pet status Pending" ".status" "Pending" + + if [ -n "$ADOPT_ID_1" ]; then + check "Cancel adoption" 200 PUT "/adoptions/$ADOPT_ID_1" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cancelled\"}" + check "Pet back to Available" 200 GET "/pets/$AVAIL_PET_1" + check_field "Pet status Available" ".status" "Available" + fi +fi + +if [ -n "$AVAIL_PET_2" ]; then + check "Staff create adoption 2" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST adopt2\"}" + ADOPT_ID_2=$(id_from_body) + [ -n "$ADOPT_ID_2" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_2") + + if [ -n "$ADOPT_ID_2" ]; then + check "Complete adoption" 200 PUT "/adoptions/$ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"storeId\":1,\"status\":\"Completed\",\"notes\":\"QA_TEST completed\"}" + check "Pet now Adopted" 200 GET "/pets/$AVAIL_PET_2" + check_field "Pet status Adopted" ".status" "Adopted" + fi +fi + +if [ -n "$AVAIL_PET_3" ]; then + check "Customer request adoption" 201 POST "/adoptions" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"storeId\":1,\"notes\":\"QA_TEST customer adopt\"}" + ADOPT_ID_3=$(id_from_body) + [ -n "$ADOPT_ID_3" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_3") + + if [ -n "$ADOPT_ID_3" ]; then + check "Cancel customer adoption" 200 PUT "/adoptions/$ADOPT_ID_3" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cust cancelled\"}" + check "Pet Available again" 200 GET "/pets/$AVAIL_PET_3" + check_field "Pet Available after cancel" ".status" "Available" + fi + + check "Adopt already pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":34,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST pending block\"}" + ADOPT_BLOCK_ID=$(id_from_body) + [ -n "$ADOPT_BLOCK_ID" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_BLOCK_ID") + check "Duplicate adoption for pending pet" 400 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":35,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST dup\"}" +fi + +check "GET /adoptions (staff)" 200 GET "/adoptions" "${S[@]}" +check "GET /adoptions (customer)" 200 GET "/adoptions" "${C[@]}" + +echo "--- 10. SALES ---" + +check "GET /sales (admin)" 200 GET "/sales" "${A[@]}" +check "GET /sales/1" 200 GET "/sales/1" "${A[@]}" +check "GET /sales/my (customer)" 200 GET "/sales/my" "${C[@]}" +check "GET /sales (customer)" 403 GET "/sales" "${C[@]}" +check "GET /sales/999999" 404 GET "/sales/999999" "${A[@]}" + +curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_inv_before.json 2>/dev/null +INV_BEFORE=$(jq -r '.content[] | select(.productId == 1) | .quantity // 0' /tmp/qa_inv_before.json 2>/dev/null | head -1) + +check "Staff create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":1}],"notes":"QA_TEST sale"}' +SALE_ID=$(id_from_body) +[ -n "$SALE_ID" ] && CLEANUP_SALE_IDS+=("$SALE_ID") + +if [ -n "$INV_BEFORE" ] && [ "$INV_BEFORE" != "" ] && [ "$INV_BEFORE" != "null" ]; then + curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_inv_after.json 2>/dev/null + INV_AFTER=$(jq -r '.content[] | select(.productId == 1) | .quantity // 0' /tmp/qa_inv_after.json 2>/dev/null | head -1) + if [ -n "$INV_AFTER" ] && [ "$INV_AFTER" != "null" ] && [ "$((INV_BEFORE - 1))" = "$INV_AFTER" ]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)); echo "FAIL: Inventory decrease — before=$INV_BEFORE after=$INV_AFTER" + fi +fi + +check "Sale non-existent product" 404 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":999999,"quantity":1}]}' +check "Sale zero quantity" 400 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":0}]}' +check "Sale (no auth)" 401 POST "/sales" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":1}]}' +check "Sale (customer)" 403 POST "/sales" "${C[@]}" "${J[@]}" -d '{"storeId":1,"customerId":15,"items":[{"productId":1,"quantity":1}]}' + +check "Staff create sale 2" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":2,"quantity":1}],"notes":"QA_TEST sale2"}' +SALE_ID_2=$(id_from_body) +[ -n "$SALE_ID_2" ] && CLEANUP_SALE_IDS+=("$SALE_ID_2") + +check "GET /sales filtered by store" 200 GET "/sales?storeId=1" "${A[@]}" +check "GET /sales paginated" 200 GET "/sales?page=0&size=5" "${A[@]}" + +echo "--- 11. CART ---" + +check "GET /cart (customer)" 200 GET "/cart?storeId=1" "${C[@]}" + +check "Add cart item" 200 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":1}' +if [ "$(jq -r '.status // empty' /tmp/qa_body.json 2>/dev/null)" = "" ]; then + check "Add cart item alt" 201 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":2,"storeId":1,"quantity":1}' +fi + +check "GET cart with item" 200 GET "/cart?storeId=1" "${C[@]}" +CART_SUBTOTAL=$(jq -r '.subtotal // .totalBeforeDiscount // 0' /tmp/qa_body.json 2>/dev/null) +CART_ITEM_ID=$(jq -r '.items[0].id // .items[0].cartItemId // empty' /tmp/qa_body.json 2>/dev/null) + +if [ -n "$CART_ITEM_ID" ]; then + check "Update cart qty" 200 PUT "/cart/items/$CART_ITEM_ID" "${C[@]}" "${J[@]}" -d '{"quantity":2}' + check "GET cart after qty update" 200 GET "/cart?storeId=1" "${C[@]}" +fi + +check "Apply coupon WELCOME10" 200 POST "/cart/coupon" "${C[@]}" "${J[@]}" -d '{"couponCode":"WELCOME10","storeId":1}' +check "GET cart with coupon" 200 GET "/cart?storeId=1" "${C[@]}" +DISCOUNT=$(jq -r '.discount // .totalDiscount // 0' /tmp/qa_body.json 2>/dev/null) + +check "Remove coupon" 200 DELETE "/cart/coupon?storeId=1" "${C[@]}" +check "GET cart no coupon" 200 GET "/cart?storeId=1" "${C[@]}" + +check "Apply points" 200 POST "/cart/points" "${C[@]}" "${J[@]}" -d '{"points":10,"storeId":1}' +check "Remove points" 200 DELETE "/cart/points?storeId=1" "${C[@]}" + +if [ -n "$CART_ITEM_ID" ]; then + check "Remove cart item" 200 DELETE "/cart/items/$CART_ITEM_ID" "${C[@]}" +fi + +check "Clear cart" 200 DELETE "/cart?storeId=1" "${C[@]}" + +check "Add item qty 0" 400 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":0}' +check "Apply invalid coupon" 400 POST "/cart/coupon" "${C[@]}" "${J[@]}" -d '{"couponCode":"FAKECOUPON999","storeId":1}' +check "Checkout empty cart" 400 POST "/cart/checkout" "${C[@]}" "${J[@]}" -d '{"storeId":1}' + +check "Add item for checkout test" 200 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":1}' +check "Checkout" 200 POST "/cart/checkout" "${C[@]}" "${J[@]}" -d '{"storeId":1}' +CHECKOUT_SALE_ID=$(jq -r '.id // .saleId // empty' /tmp/qa_body.json 2>/dev/null) +[ -n "$CHECKOUT_SALE_ID" ] && CLEANUP_SALE_IDS+=("$CHECKOUT_SALE_ID") + +check "Clear cart final" 200 DELETE "/cart?storeId=1" "${C[@]}" + +echo "--- 12. REFUNDS ---" + +check "GET /refunds (admin)" 200 GET "/refunds" "${A[@]}" +check "GET /refunds (customer)" 200 GET "/refunds" "${C[@]}" + +CUST_SALE_ID="" +if [ -n "$CHECKOUT_SALE_ID" ]; then + CUST_SALE_ID="$CHECKOUT_SALE_ID" +else + curl -s "$BASE/sales/my" "${C[@]}" -o /tmp/qa_my_sales.json 2>/dev/null + CUST_SALE_ID=$(jq -r '.content[0].id // .[0].id // empty' /tmp/qa_my_sales.json 2>/dev/null) +fi + +if [ -n "$CUST_SALE_ID" ]; then + check "Customer create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CUST_SALE_ID,\"reason\":\"QA_TEST refund reason\"}" + REFUND_ID=$(id_from_body) + + if [ -n "$REFUND_ID" ]; then + check "Staff approve refund" 200 PUT "/refunds/$REFUND_ID" "${S[@]}" "${J[@]}" -d "{\"status\":\"Approved\",\"saleId\":$CUST_SALE_ID,\"reason\":\"QA_TEST approved\"}" + check_field "Refund approved" ".status" "Approved" + fi +fi + +check "Refund non-existent sale" 404 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":999999,"reason":"QA_TEST nope"}' +check "Refund missing reason" 400 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":1}' +check "GET /refunds (staff)" 200 GET "/refunds" "${S[@]}" +check "GET /refunds paginated" 200 GET "/refunds?page=0&size=5" "${A[@]}" + +echo "--- 13. COUPONS ---" + +check "GET /coupons (admin)" 200 GET "/coupons" "${A[@]}" +check "GET /coupons/1" 200 GET "/coupons/1" "${A[@]}" +check "GET /coupons/code/WELCOME10" 200 GET "/coupons/code/WELCOME10" "${A[@]}" +check "GET /coupons (customer)" 403 GET "/coupons" "${C[@]}" + +check "Create coupon" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_CPN","discountPercent":10,"description":"QA test coupon","expiryDate":"2028-12-31","maxUses":100}' +CPN_ID=$(id_from_body) +[ -n "$CPN_ID" ] && CLEANUP_COUPON_IDS+=("$CPN_ID") +check_field "Coupon code" ".code" "QA_TEST_CPN" + +if [ -n "$CPN_ID" ]; then + check "Update coupon" 200 PUT "/coupons/$CPN_ID" "${A[@]}" "${J[@]}" -d "{\"code\":\"QA_TEST_CPN\",\"discountPercent\":15,\"description\":\"updated\",\"expiryDate\":\"2028-12-31\",\"maxUses\":50}" +fi + +check "Create coupon to delete" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_DEL","discountPercent":5,"description":"del","expiryDate":"2028-12-31","maxUses":10}' +DEL_CPN_ID=$(id_from_body) +if [ -n "$DEL_CPN_ID" ]; then + check "Delete coupon" 200 DELETE "/coupons/$DEL_CPN_ID" "${A[@]}" +fi + +check "Coupon 100% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_100","discountPercent":100,"description":"bad","expiryDate":"2028-12-31","maxUses":1}' +check "Coupon 150% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_150","discountPercent":150,"description":"bad","expiryDate":"2028-12-31","maxUses":1}' + +check "Coupon 99.99%" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_99","discountPercent":99.99,"description":"boundary","expiryDate":"2028-12-31","maxUses":1}' +BOUNDARY_CPN_ID=$(id_from_body) +[ -n "$BOUNDARY_CPN_ID" ] && CLEANUP_COUPON_IDS+=("$BOUNDARY_CPN_ID") + +check "Duplicate coupon code" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"WELCOME10","discountPercent":5,"description":"dup","expiryDate":"2028-12-31","maxUses":1}' +check "Create coupon (customer)" 403 POST "/coupons" "${C[@]}" "${J[@]}" -d '{"code":"QA_TEST_NOPE","discountPercent":5,"description":"nope","expiryDate":"2028-12-31","maxUses":1}' + +echo "--- 14. SUPPLIERS / PRODUCT-SUPPLIERS / INVENTORY / PURCHASE-ORDERS ---" + +check "GET /suppliers (admin)" 200 GET "/suppliers" "${A[@]}" +check "GET /suppliers/1" 200 GET "/suppliers/1" "${A[@]}" +check "GET /suppliers (customer)" 403 GET "/suppliers" "${C[@]}" + +check "Create supplier" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supplierName":"QA_TEST_Supplier","contactName":"QA Contact","email":"qa_supplier@test.com","phone":"5553333333","address":"100 Supply Rd"}' +SUPP_ID=$(id_from_body) +[ -n "$SUPP_ID" ] && CLEANUP_SUPPLIER_IDS+=("$SUPP_ID") + +if [ -n "$SUPP_ID" ]; then + check "Update supplier" 200 PUT "/suppliers/$SUPP_ID" "${A[@]}" "${J[@]}" -d "{\"supplierName\":\"QA_TEST_SuppUpd\",\"contactName\":\"QA Updated\",\"email\":\"qa_supp_upd@test.com\",\"phone\":\"5553333334\",\"address\":\"101 Supply Rd\"}" +fi + +check "Create supplier to delete" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supplierName":"QA_TEST_SuppDel","contactName":"Del","email":"del@test.com","phone":"5550000000","address":"x"}' +DEL_SUPP_ID=$(id_from_body) +if [ -n "$DEL_SUPP_ID" ]; then + check "Delete supplier" 200 DELETE "/suppliers/$DEL_SUPP_ID" "${A[@]}" +fi + +check "GET /product-suppliers (admin)" 200 GET "/product-suppliers" "${A[@]}" +check "GET /inventory (admin)" 200 GET "/inventory" "${A[@]}" +check "GET /inventory/1" 200 GET "/inventory/1" "${A[@]}" +check "GET /purchase-orders (admin)" 200 GET "/purchase-orders" "${A[@]}" +check "GET /purchase-orders (customer)" 403 GET "/purchase-orders" "${C[@]}" +check "GET /inventory (customer)" 403 GET "/inventory" "${C[@]}" + +echo "--- 15. CHAT ---" + +check "GET /chat/conversations (customer)" 200 GET "/chat/conversations" "${C[@]}" + +check "Create conversation" 201 POST "/chat/conversations" "${C[@]}" "${J[@]}" -d '{}' +CONV_ID=$(id_from_body) +[ -n "$CONV_ID" ] && CLEANUP_CONV_IDS+=("$CONV_ID") + +if [ -n "$CONV_ID" ]; then + check "Send message" 201 POST "/chat/conversations/$CONV_ID/messages" "${C[@]}" "${J[@]}" -d '{"content":"Hello from QA_TEST"}' + + check "Send empty message" 400 POST "/chat/conversations/$CONV_ID/messages" "${C[@]}" "${J[@]}" -d '{"content":""}' + check "Send null content" 400 POST "/chat/conversations/$CONV_ID/messages" "${C[@]}" "${J[@]}" -d '{}' + + check "GET messages" 200 GET "/chat/conversations/$CONV_ID/messages" "${C[@]}" + + check "Request human takeover" 200 POST "/chat/conversations/$CONV_ID/takeover" "${C[@]}" "${J[@]}" -d '{}' + + check "Staff view conversations" 200 GET "/chat/conversations" "${S[@]}" + + check "Staff send reply" 201 POST "/chat/conversations/$CONV_ID/messages" "${S[@]}" "${J[@]}" -d '{"content":"Staff reply QA_TEST"}' + + check "GET conversation detail" 200 GET "/chat/conversations/$CONV_ID" "${S[@]}" + + check "Close conversation" 200 PUT "/chat/conversations/$CONV_ID/close" "${S[@]}" "${J[@]}" -d '{}' + check "GET closed conversation" 200 GET "/chat/conversations/$CONV_ID" "${C[@]}" + check_field "Conversation closed" ".status" "CLOSED" + + check "Send to closed" 400 POST "/chat/conversations/$CONV_ID/messages" "${C[@]}" "${J[@]}" -d '{"content":"should fail"}' +fi + +check "GET /chat/conversations (admin)" 200 GET "/chat/conversations" "${A[@]}" +check "Create conv 2" 201 POST "/chat/conversations" "${C[@]}" "${J[@]}" -d '{}' +CONV_ID_2=$(id_from_body) +[ -n "$CONV_ID_2" ] && CLEANUP_CONV_IDS+=("$CONV_ID_2") +check "Send message conv 2" 201 POST "/chat/conversations/$CONV_ID_2/messages" "${C[@]}" "${J[@]}" -d '{"content":"QA_TEST second conv"}' + +echo "--- 16. ACTIVITY LOGS / ANALYTICS / DROPDOWNS / HEALTH ---" + +check "GET /health" 200 GET "/health" +check "GET /activity-logs (admin)" 200 GET "/activity-logs" "${A[@]}" +check "GET /activity-logs (customer)" 403 GET "/activity-logs" "${C[@]}" +check "GET /analytics/dashboard (admin)" 200 GET "/analytics/dashboard" "${A[@]}" +check "GET /analytics/dashboard (customer)" 403 GET "/analytics/dashboard" "${C[@]}" +check "GET /analytics/dashboard?days=1" 200 GET "/analytics/dashboard?days=1" "${A[@]}" + +for ep in "dropdowns/pets" "dropdowns/services" "dropdowns/products" "dropdowns/categories" \ + "dropdowns/product-categories" "dropdowns/pet-species" "dropdowns/pet-breeds" \ + "dropdowns/stores" "dropdowns/customers" "dropdowns/adoption-pets" \ + "dropdowns/employees" "dropdowns/suppliers"; do + check "GET /$ep" 200 GET "/$ep" "${A[@]}" +done + +check "GET /dropdowns/stores/1/employees" 200 GET "/dropdowns/stores/1/employees" "${A[@]}" +check "GET /dropdowns/customers/32/pets" 200 GET "/dropdowns/customers/32/pets" "${A[@]}" + +echo "--- 17. CROSS-ENTITY ---" + +curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv1.json 2>/dev/null +CROSS_INV_BEFORE=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv1.json 2>/dev/null | head -1) + +check "Cross: create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":2,"quantity":1}],"notes":"QA_TEST_CROSS"}' +CROSS_SALE_ID=$(id_from_body) +[ -n "$CROSS_SALE_ID" ] && CLEANUP_SALE_IDS+=("$CROSS_SALE_ID") + +if [ -n "$CROSS_INV_BEFORE" ] && [ "$CROSS_INV_BEFORE" != "null" ] && [ "$CROSS_INV_BEFORE" != "" ]; then + curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv2.json 2>/dev/null + CROSS_INV_AFTER=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv2.json 2>/dev/null | head -1) + if [ "$((CROSS_INV_BEFORE - 1))" = "$CROSS_INV_AFTER" ]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)); echo "FAIL: Cross inventory decrease — before=$CROSS_INV_BEFORE after=$CROSS_INV_AFTER" + fi +fi + +if [ -n "$CROSS_SALE_ID" ]; then + check "Cross: create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CROSS_SALE_ID,\"reason\":\"QA_TEST cross refund\"}" + CROSS_REFUND_ID=$(id_from_body) + if [ -n "$CROSS_REFUND_ID" ]; then + check "Cross: approve refund" 200 PUT "/refunds/$CROSS_REFUND_ID" "${S[@]}" "${J[@]}" -d "{\"status\":\"Approved\",\"saleId\":$CROSS_SALE_ID,\"reason\":\"QA_TEST cross approved\"}" + curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv3.json 2>/dev/null + CROSS_INV_RESTORED=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv3.json 2>/dev/null | head -1) + if [ -n "$CROSS_INV_RESTORED" ] && [ "$CROSS_INV_RESTORED" = "$CROSS_INV_BEFORE" ]; then + PASS=$((PASS+1)) + else + FAIL=$((FAIL+1)); echo "FAIL: Cross inventory restore — expected=$CROSS_INV_BEFORE got=$CROSS_INV_RESTORED" + fi + fi +fi + +curl -s "$BASE/pets?status=Available&size=5&page=1" "${A[@]}" -o /tmp/qa_cross_avail.json 2>/dev/null +CROSS_PET=$(jq -r '.content[0].id // empty' /tmp/qa_cross_avail.json 2>/dev/null) + +if [ -n "$CROSS_PET" ]; then + check "Cross: create adoption Pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST cross adopt\"}" + CROSS_ADOPT_ID=$(id_from_body) + [ -n "$CROSS_ADOPT_ID" ] && CLEANUP_ADOPTION_IDS+=("$CROSS_ADOPT_ID") + + check "Cross: pet is Pending" 200 GET "/pets/$CROSS_PET" + check_field "Cross pet Pending" ".status" "Pending" + + if [ -n "$CROSS_ADOPT_ID" ]; then + check "Cross: complete adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"storeId\":1,\"status\":\"Completed\",\"notes\":\"QA_TEST cross completed\"}" + check "Cross: pet is Adopted" 200 GET "/pets/$CROSS_PET" + check_field "Cross pet Adopted" ".status" "Adopted" + fi +fi + +curl -s "$BASE/pets?status=Available&size=5&page=2" "${A[@]}" -o /tmp/qa_cross_avail2.json 2>/dev/null +CROSS_PET_2=$(jq -r '.content[0].id // empty' /tmp/qa_cross_avail2.json 2>/dev/null) + +if [ -n "$CROSS_PET_2" ]; then + check "Cross: adopt then cancel" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST cross cancel\"}" + CROSS_ADOPT_ID_2=$(id_from_body) + [ -n "$CROSS_ADOPT_ID_2" ] && CLEANUP_ADOPTION_IDS+=("$CROSS_ADOPT_ID_2") + + if [ -n "$CROSS_ADOPT_ID_2" ]; then + check "Cross: cancel adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cross cancelled\"}" + check "Cross: pet Available" 200 GET "/pets/$CROSS_PET_2" + check_field "Cross pet Available" ".status" "Available" + fi +fi + +echo "" +echo "--- CLEANUP ---" + +for id in "${CLEANUP_CONV_IDS[@]}"; do + curl -s -o /dev/null -X PUT "$BASE/chat/conversations/$id/close" "${S[@]}" "${J[@]}" -d '{}' 2>/dev/null +done + +for id in "${CLEANUP_ADOPTION_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/adoptions/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_APPT_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/appointments/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_SALE_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/sales/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_MYPET_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/my-pets/$id" "${C[@]}" 2>/dev/null +done + +for id in "${CLEANUP_PET_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/pets/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_SERVICE_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/services/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_PRODUCT_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/products/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_CATEGORY_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/categories/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_STORE_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/stores/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_COUPON_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/coupons/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_SUPPLIER_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/suppliers/$id" "${A[@]}" 2>/dev/null +done + +for id in "${CLEANUP_USER_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/users/$id" "${A[@]}" 2>/dev/null +done + +curl -s -o /dev/null -X DELETE "$BASE/users" "${A[@]}" -G -d "username=testuser_qa" 2>/dev/null + +rm -f /tmp/qa_body.json /tmp/qa_avail.json /tmp/qa_inv_before.json /tmp/qa_inv_after.json +rm -f /tmp/qa_my_sales.json /tmp/qa_cross_inv1.json /tmp/qa_cross_inv2.json /tmp/qa_cross_inv3.json +rm -f /tmp/qa_cross_avail.json /tmp/qa_cross_avail2.json + +echo "" +echo "=========================================" +echo "RESULTS: $PASS passed, $FAIL failed" +echo "=========================================" -- 2.49.1 From 0b8bf02bd4a9c7aa830a68f73d8dc6ba8a45982a Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 07:33:05 -0600 Subject: [PATCH 20/42] Fix test DTO fields --- test-backend-full.sh | 234 +++++++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/test-backend-full.sh b/test-backend-full.sh index ad8cbf75..21535d77 100755 --- a/test-backend-full.sh +++ b/test-backend-full.sh @@ -50,7 +50,7 @@ check_field_gt() { else FAIL=$((FAIL+1)); echo "FAIL: $label — expected > $threshold got '$actual'"; fi } -id_from_body() { jq -r '.id // .userId // empty' /tmp/qa_body.json 2>/dev/null; } +id_from_body() { jq -r '.id // .userId // .prodId // .categoryId // .petId // .serviceId // .storeId // .appointmentId // .adoptionId // .saleId // .couponId // .supId // empty' /tmp/qa_body.json 2>/dev/null; } echo "=========================================" echo " PET SHOP QA — Full Backend Test Suite" @@ -109,21 +109,21 @@ check "GET /products?q=dog" 200 GET "/products?q=dog" check "GET /products?categoryId=1" 200 GET "/products?categoryId=1" check "GET /products/999999" 404 GET "/products/999999" -check "Create product (admin)" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Product","description":"QA test","price":9.99,"categoryId":1}' +check "Create product (admin)" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Product","prodDesc":"QA test","prodPrice":9.99,"categoryId":1}' PROD_ID=$(id_from_body) [ -n "$PROD_ID" ] && CLEANUP_PRODUCT_IDS+=("$PROD_ID") check_field "Product name" ".prodName" "QA_TEST_Product" -check "Create product (customer)" 403 POST "/products" "${C[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Nope","price":1,"categoryId":1}' -check "Create product (no auth)" 401 POST "/products" "${J[@]}" -d '{"prodName":"QA_TEST_Nope2","price":1,"categoryId":1}' -check "Create product missing name" 400 POST "/products" "${A[@]}" "${J[@]}" -d '{"price":1,"categoryId":1}' +check "Create product (customer)" 403 POST "/products" "${C[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_Nope","prodPrice":1,"categoryId":1}' +check "Create product (no auth)" 401 POST "/products" "${J[@]}" -d '{"prodName":"QA_TEST_Nope2","prodPrice":1,"categoryId":1}' +check "Create product missing name" 400 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodPrice":1,"categoryId":1}' if [ -n "$PROD_ID" ]; then - check "Update product" 200 PUT "/products/$PROD_ID" "${A[@]}" "${J[@]}" -d "{\"prodName\":\"QA_TEST_Updated\",\"description\":\"updated\",\"price\":19.99,\"categoryId\":1}" + check "Update product" 200 PUT "/products/$PROD_ID" "${A[@]}" "${J[@]}" -d "{\"prodName\":\"QA_TEST_Updated\",\"prodDesc\":\"updated\",\"prodPrice\":19.99,\"categoryId\":1}" check_field "Updated name" ".prodName" "QA_TEST_Updated" fi -check "Create product to delete" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_DeleteMe","description":"del","price":1,"categoryId":1}' +check "Create product to delete" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TEST_DeleteMe","prodDesc":"del","prodPrice":1,"categoryId":1}' DEL_PROD_ID=$(id_from_body) if [ -n "$DEL_PROD_ID" ]; then check "Delete product" 200 DELETE "/products/$DEL_PROD_ID" "${A[@]}" @@ -131,7 +131,7 @@ if [ -n "$DEL_PROD_ID" ]; then fi check "GET /products paginated" 200 GET "/products?page=0&size=5" -check "GET /products sorted" 200 GET "/products?sort=price,desc" +check "GET /products sorted" 200 GET "/products?sort=prodName,asc" echo "--- 3. CATEGORIES ---" @@ -139,19 +139,19 @@ check "GET /categories" 200 GET "/categories" check "GET /categories/1" 200 GET "/categories/1" check "GET /categories/999999" 404 GET "/categories/999999" -check "Create category (admin)" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"catName":"QA_TEST_Category","description":"qa"}' +check "Create category (admin)" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"categoryName":"QA_TEST_Category","categoryType":"qa"}' CAT_ID=$(id_from_body) [ -n "$CAT_ID" ] && CLEANUP_CATEGORY_IDS+=("$CAT_ID") -check "Create category (customer)" 403 POST "/categories" "${C[@]}" "${J[@]}" -d '{"catName":"QA_TEST_Nope"}' -check "Create category missing name" 400 POST "/categories" "${A[@]}" "${J[@]}" -d '{"description":"no name"}' +check "Create category (customer)" 403 POST "/categories" "${C[@]}" "${J[@]}" -d '{"categoryName":"QA_TEST_Nope"}' +check "Create category missing name" 400 POST "/categories" "${A[@]}" "${J[@]}" -d '{"categoryType":"no name"}' if [ -n "$CAT_ID" ]; then - check "Update category" 200 PUT "/categories/$CAT_ID" "${A[@]}" "${J[@]}" -d "{\"catName\":\"QA_TEST_CatUpdated\",\"description\":\"updated\"}" - check_field "Updated cat name" ".catName" "QA_TEST_CatUpdated" + check "Update category" 200 PUT "/categories/$CAT_ID" "${A[@]}" "${J[@]}" -d "{\"categoryName\":\"QA_TEST_CatUpdated\",\"categoryType\":\"updated\"}" + check_field "Updated cat name" ".categoryName" "QA_TEST_CatUpdated" fi -check "Create category to delete" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"catName":"QA_TEST_CatDel","description":"del"}' +check "Create category to delete" 201 POST "/categories" "${A[@]}" "${J[@]}" -d '{"categoryName":"QA_TEST_CatDel","categoryType":"del"}' DEL_CAT_ID=$(id_from_body) if [ -n "$DEL_CAT_ID" ]; then check "Delete category" 200 DELETE "/categories/$DEL_CAT_ID" "${A[@]}" @@ -167,31 +167,31 @@ check "GET /pets?status=Available" 200 GET "/pets?status=Available" check "GET /pets/999999" 404 GET "/pets/999999" check "GET /my-pets (customer)" 200 GET "/my-pets" "${C[@]}" -check "Admin create pet" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"name":"QA_TEST_Pet","species":"Dog","breed":"Labrador","age":2,"gender":"Male","status":"Available","storeId":1,"price":100,"description":"QA test pet"}' +check "Admin create pet" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"petName":"QA_TEST_Pet","petSpecies":"Dog","petBreed":"Labrador","petAge":2,"petStatus":"Available","storeId":1,"petPrice":100}' PET_ID=$(id_from_body) [ -n "$PET_ID" ] && CLEANUP_PET_IDS+=("$PET_ID") -check_field "Pet name" ".name" "QA_TEST_Pet" -check_field "Pet species" ".species" "Dog" +check_field "Pet name" ".petName" "QA_TEST_Pet" +check_field "Pet species" ".petSpecies" "Dog" if [ -n "$PET_ID" ]; then - check "Update pet" 200 PUT "/pets/$PET_ID" "${A[@]}" "${J[@]}" -d "{\"name\":\"QA_TEST_PetUpd\",\"species\":\"Dog\",\"breed\":\"Labrador\",\"age\":3,\"gender\":\"Male\",\"status\":\"Available\",\"storeId\":1,\"price\":150,\"description\":\"updated\"}" - check_field "Updated pet name" ".name" "QA_TEST_PetUpd" + check "Update pet" 200 PUT "/pets/$PET_ID" "${A[@]}" "${J[@]}" -d "{\"petName\":\"QA_TEST_PetUpd\",\"petSpecies\":\"Dog\",\"petBreed\":\"Labrador\",\"petAge\":3,\"petStatus\":\"Available\",\"storeId\":1,\"petPrice\":150}" + check_field "Updated pet name" ".petName" "QA_TEST_PetUpd" fi -check "Admin create pet to delete" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"name":"QA_TEST_PetDel","species":"Cat","breed":"Siamese","age":1,"gender":"Female","status":"Available","storeId":1,"price":50,"description":"del"}' +check "Admin create pet to delete" 201 POST "/pets" "${A[@]}" "${J[@]}" -d '{"petName":"QA_TEST_PetDel","petSpecies":"Cat","petBreed":"Siamese","petAge":1,"petStatus":"Available","storeId":1,"petPrice":50}' DEL_PET_ID=$(id_from_body) if [ -n "$DEL_PET_ID" ]; then check "Delete pet" 200 DELETE "/pets/$DEL_PET_ID" "${A[@]}" check "GET deleted pet" 404 GET "/pets/$DEL_PET_ID" fi -check "Customer create my-pet" 201 POST "/my-pets" "${C[@]}" "${J[@]}" -d '{"name":"QA_TEST_MyPet","species":"Rabbit","breed":"Holland Lop","age":1,"gender":"Female","description":"my pet"}' +check "Customer create my-pet" 201 POST "/my-pets" "${C[@]}" "${J[@]}" -d '{"petName":"QA_TEST_MyPet","species":"Rabbit","breed":"Holland Lop","petAge":1}' MY_PET_ID=$(id_from_body) [ -n "$MY_PET_ID" ] && CLEANUP_MYPET_IDS+=("$MY_PET_ID") check "GET /my-pets has new pet" 200 GET "/my-pets" "${C[@]}" -check "Create pet (customer direct)" 403 POST "/pets" "${C[@]}" "${J[@]}" -d '{"name":"QA_TEST_Nope","species":"Dog","breed":"Lab","age":1,"gender":"Male","status":"Available","storeId":1,"price":10}' -check "Create pet (no auth)" 401 POST "/pets" "${J[@]}" -d '{"name":"QA_TEST_Nope2","species":"Dog","breed":"Lab","age":1,"gender":"Male","status":"Available","storeId":1,"price":10}' +check "Create pet (customer direct)" 403 POST "/pets" "${C[@]}" "${J[@]}" -d '{"petName":"QA_TEST_Nope","petSpecies":"Dog","petBreed":"Lab","petAge":1,"petStatus":"Available","storeId":1,"petPrice":10}' +check "Create pet (no auth)" 401 POST "/pets" "${J[@]}" -d '{"petName":"QA_TEST_Nope2","petSpecies":"Dog","petBreed":"Lab","petAge":1,"petStatus":"Available","storeId":1,"petPrice":10}' check "GET /pets paginated" 200 GET "/pets?page=0&size=5" echo "--- 5. SERVICES ---" @@ -201,21 +201,21 @@ check "GET /services/1" 200 GET "/services/1" check "GET /services?species=Dog" 200 GET "/services?species=Dog" check "GET /services/999999" 404 GET "/services/999999" -check "Create service (admin)" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Service","description":"qa svc","basePrice":25.00,"duration":30,"species":["Dog","Cat"]}' +check "Create service (admin)" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Service","serviceDesc":"qa svc","servicePrice":25.00,"serviceDuration":30,"species":["Dog","Cat"]}' SVC_ID=$(id_from_body) [ -n "$SVC_ID" ] && CLEANUP_SERVICE_IDS+=("$SVC_ID") check_field "Service name" ".serviceName" "QA_TEST_Service" if [ -n "$SVC_ID" ]; then - check "Update service" 200 PUT "/services/$SVC_ID" "${A[@]}" "${J[@]}" -d "{\"serviceName\":\"QA_TEST_SvcUpd\",\"description\":\"upd\",\"basePrice\":30,\"duration\":45,\"species\":[\"Dog\"]}" + check "Update service" 200 PUT "/services/$SVC_ID" "${A[@]}" "${J[@]}" -d "{\"serviceName\":\"QA_TEST_SvcUpd\",\"serviceDesc\":\"upd\",\"servicePrice\":30,\"serviceDuration\":45,\"species\":[\"Dog\"]}" fi -check "Create service to delete" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_SvcDel","description":"del","basePrice":10,"duration":15,"species":["Cat"]}' +check "Create service to delete" 201 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_SvcDel","serviceDesc":"del","servicePrice":10,"serviceDuration":15,"species":["Cat"]}' DEL_SVC_ID=$(id_from_body) if [ -n "$DEL_SVC_ID" ]; then check "Delete service" 200 DELETE "/services/$DEL_SVC_ID" "${A[@]}" fi -check "Create service (customer)" 403 POST "/services" "${C[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Nope","basePrice":10,"duration":10,"species":["Dog"]}' +check "Create service (customer)" 403 POST "/services" "${C[@]}" "${J[@]}" -d '{"serviceName":"QA_TEST_Nope","servicePrice":10,"serviceDuration":10,"species":["Dog"]}' echo "--- 6. STORES ---" @@ -223,21 +223,21 @@ check "GET /stores" 200 GET "/stores" check "GET /stores/1" 200 GET "/stores/1" check "GET /stores/999999" 404 GET "/stores/999999" -check "Create store (admin)" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Store","address":"123 QA St","city":"Testville","state":"QA","zipCode":"00000","phone":"5559999999"}' +check "Create store (admin)" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Store","address":"123 QA St","phone":"5559999999","email":"qastore@test.com"}' STORE_ID=$(id_from_body) [ -n "$STORE_ID" ] && CLEANUP_STORE_IDS+=("$STORE_ID") if [ -n "$STORE_ID" ]; then - check "Update store" 200 PUT "/stores/$STORE_ID" "${A[@]}" "${J[@]}" -d "{\"storeName\":\"QA_TEST_StoreUpd\",\"address\":\"456 QA Ave\",\"city\":\"Testville\",\"state\":\"QA\",\"zipCode\":\"00001\",\"phone\":\"5559999998\"}" + check "Update store" 200 PUT "/stores/$STORE_ID" "${A[@]}" "${J[@]}" -d "{\"storeName\":\"QA_TEST_StoreUpd\",\"address\":\"456 QA Ave\",\"phone\":\"5559999998\",\"email\":\"qastoreupd@test.com\"}" fi -check "Create store to delete" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_StoreDel","address":"789 Del Rd","city":"Gone","state":"QA","zipCode":"00002","phone":"5559999997"}' +check "Create store to delete" 201 POST "/stores" "${A[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_StoreDel","address":"789 Del Rd","phone":"5559999997","email":"qastoredel@test.com"}' DEL_STORE_ID=$(id_from_body) if [ -n "$DEL_STORE_ID" ]; then check "Delete store" 200 DELETE "/stores/$DEL_STORE_ID" "${A[@]}" fi -check "Create store (staff)" 403 POST "/stores" "${S[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Nope","address":"x","city":"x","state":"x","zipCode":"x","phone":"x"}' +check "Create store (staff)" 403 POST "/stores" "${S[@]}" "${J[@]}" -d '{"storeName":"QA_TEST_Nope","address":"x","phone":"x","email":"x@x.com"}' echo "--- 7. USERS / EMPLOYEES / CUSTOMERS ---" @@ -276,34 +276,34 @@ check "GET /appointments?status=Scheduled" 200 GET "/appointments?status=Schedul check "GET /appointments/availability" 200 GET "/appointments/availability?storeId=1&serviceId=1&date=2027-06-01" "${A[@]}" check "GET /appointments/999999" 404 GET "/appointments/999999" "${A[@]}" -check "Create appointment" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-06-15","appointmentTime":"10:00","notes":"QA_TEST"}' +check "Create appointment" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-06-15","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' APPT_ID=$(id_from_body) [ -n "$APPT_ID" ] && CLEANUP_APPT_IDS+=("$APPT_ID") -check_field "Appointment status" ".status" "Scheduled" +check_field "Appointment status" ".appointmentStatus" "Scheduled" if [ -n "$APPT_ID" ]; then - check "Cancel appointment" 200 PUT "/appointments/$APPT_ID" "${A[@]}" "${J[@]}" -d "{\"petId\":53,\"customerId\":32,\"storeId\":2,\"employeeId\":7,\"serviceId\":1,\"appointmentDate\":\"2027-06-15\",\"appointmentTime\":\"10:00\",\"status\":\"Cancelled\",\"notes\":\"QA_TEST cancelled\"}" - check_field "Cancelled status" ".status" "Cancelled" + check "Cancel appointment" 200 PUT "/appointments/$APPT_ID" "${A[@]}" "${J[@]}" -d "{\"petId\":53,\"customerId\":32,\"storeId\":2,\"employeeId\":7,\"serviceId\":1,\"appointmentDate\":\"2027-06-15\",\"appointmentTime\":\"10:00\",\"appointmentStatus\":\"Cancelled\"}" + check_field "Cancelled status" ".appointmentStatus" "Cancelled" fi -check "Create appointment to delete" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-07-01","appointmentTime":"14:00","notes":"QA_TEST_DEL"}' +check "Create appointment to delete" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-07-01","appointmentTime":"14:00","appointmentStatus":"Scheduled"}' DEL_APPT_ID=$(id_from_body) if [ -n "$DEL_APPT_ID" ]; then check "Delete appointment" 200 DELETE "/appointments/$DEL_APPT_ID" "${A[@]}" fi -check "Appointment past date" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2020-01-01","appointmentTime":"10:00","notes":"QA_TEST past"}' +check "Appointment past date" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2020-01-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' -check "Pet-service mismatch" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":38,"customerId":17,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-08-01","appointmentTime":"10:00","notes":"QA_TEST mismatch"}' +check "Pet-service mismatch" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":38,"customerId":17,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-08-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' -check "Create appt for duplicate test" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","notes":"QA_TEST dup1"}' +check "Create appt for duplicate test" 201 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","appointmentStatus":"Scheduled"}' DUP_APPT_ID=$(id_from_body) [ -n "$DUP_APPT_ID" ] && CLEANUP_APPT_IDS+=("$DUP_APPT_ID") -check "Duplicate time/employee" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","notes":"QA_TEST dup2"}' +check "Duplicate time/employee" 400 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":2,"employeeId":7,"serviceId":1,"appointmentDate":"2027-09-01","appointmentTime":"09:00","appointmentStatus":"Scheduled"}' check "GET /appointments (customer)" 200 GET "/appointments" "${C[@]}" check "GET /appointments (staff)" 200 GET "/appointments" "${S[@]}" -check "Create appointment (no auth)" 401 POST "/appointments" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-10-01","appointmentTime":"10:00"}' +check "Create appointment (no auth)" 401 POST "/appointments" "${J[@]}" -d '{"petId":53,"customerId":32,"storeId":1,"employeeId":4,"serviceId":1,"appointmentDate":"2027-10-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' echo "--- 9. ADOPTIONS ---" @@ -315,52 +315,52 @@ AVAIL_PET_1="" AVAIL_PET_2="" AVAIL_PET_3="" curl -s "$BASE/pets?status=Available&size=5" "${A[@]}" -o /tmp/qa_avail.json 2>/dev/null -AVAIL_PET_1=$(jq -r '.content[0].id // empty' /tmp/qa_avail.json 2>/dev/null) -AVAIL_PET_2=$(jq -r '.content[1].id // empty' /tmp/qa_avail.json 2>/dev/null) -AVAIL_PET_3=$(jq -r '.content[2].id // empty' /tmp/qa_avail.json 2>/dev/null) +AVAIL_PET_1=$(jq -r '.content[0].petId // empty' /tmp/qa_avail.json 2>/dev/null) +AVAIL_PET_2=$(jq -r '.content[1].petId // empty' /tmp/qa_avail.json 2>/dev/null) +AVAIL_PET_3=$(jq -r '.content[2].petId // empty' /tmp/qa_avail.json 2>/dev/null) if [ -n "$AVAIL_PET_1" ]; then - check "Staff create adoption (Pending)" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST adopt1\"}" + check "Staff create adoption (Pending)" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" ADOPT_ID_1=$(id_from_body) [ -n "$ADOPT_ID_1" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_1") check "Pet now Pending" 200 GET "/pets/$AVAIL_PET_1" - check_field "Pet status Pending" ".status" "Pending" + check_field "Pet status Pending" ".petStatus" "Pending" if [ -n "$ADOPT_ID_1" ]; then - check "Cancel adoption" 200 PUT "/adoptions/$ADOPT_ID_1" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cancelled\"}" + check "Cancel adoption" 200 PUT "/adoptions/$ADOPT_ID_1" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_1,\"customerId\":32,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Cancelled\"}" check "Pet back to Available" 200 GET "/pets/$AVAIL_PET_1" - check_field "Pet status Available" ".status" "Available" + check_field "Pet status Available" ".petStatus" "Available" fi fi if [ -n "$AVAIL_PET_2" ]; then - check "Staff create adoption 2" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST adopt2\"}" + check "Staff create adoption 2" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" ADOPT_ID_2=$(id_from_body) [ -n "$ADOPT_ID_2" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_2") if [ -n "$ADOPT_ID_2" ]; then - check "Complete adoption" 200 PUT "/adoptions/$ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"storeId\":1,\"status\":\"Completed\",\"notes\":\"QA_TEST completed\"}" + check "Complete adoption" 200 PUT "/adoptions/$ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_2,\"customerId\":33,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Completed\"}" check "Pet now Adopted" 200 GET "/pets/$AVAIL_PET_2" - check_field "Pet status Adopted" ".status" "Adopted" + check_field "Pet status Adopted" ".petStatus" "Adopted" fi fi if [ -n "$AVAIL_PET_3" ]; then - check "Customer request adoption" 201 POST "/adoptions" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"storeId\":1,\"notes\":\"QA_TEST customer adopt\"}" + check "Customer request adoption" 201 POST "/adoptions" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\"}" ADOPT_ID_3=$(id_from_body) [ -n "$ADOPT_ID_3" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_ID_3") if [ -n "$ADOPT_ID_3" ]; then - check "Cancel customer adoption" 200 PUT "/adoptions/$ADOPT_ID_3" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cust cancelled\"}" + check "Cancel customer adoption" 200 PUT "/adoptions/$ADOPT_ID_3" "${C[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Cancelled\"}" check "Pet Available again" 200 GET "/pets/$AVAIL_PET_3" - check_field "Pet Available after cancel" ".status" "Available" + check_field "Pet Available after cancel" ".petStatus" "Available" fi - check "Adopt already pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":34,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST pending block\"}" + check "Adopt already pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":34,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" ADOPT_BLOCK_ID=$(id_from_body) [ -n "$ADOPT_BLOCK_ID" ] && CLEANUP_ADOPTION_IDS+=("$ADOPT_BLOCK_ID") - check "Duplicate adoption for pending pet" 400 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":35,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST dup\"}" + check "Duplicate adoption for pending pet" 400 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$AVAIL_PET_3,\"customerId\":35,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" fi check "GET /adoptions (staff)" 200 GET "/adoptions" "${S[@]}" @@ -375,15 +375,15 @@ check "GET /sales (customer)" 403 GET "/sales" "${C[@]}" check "GET /sales/999999" 404 GET "/sales/999999" "${A[@]}" curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_inv_before.json 2>/dev/null -INV_BEFORE=$(jq -r '.content[] | select(.productId == 1) | .quantity // 0' /tmp/qa_inv_before.json 2>/dev/null | head -1) +INV_BEFORE=$(jq -r '.content[] | select(.prodId == 1) | .quantity // 0' /tmp/qa_inv_before.json 2>/dev/null | head -1) -check "Staff create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":1}],"notes":"QA_TEST sale"}' +check "Staff create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":1,"quantity":1}]}' SALE_ID=$(id_from_body) [ -n "$SALE_ID" ] && CLEANUP_SALE_IDS+=("$SALE_ID") if [ -n "$INV_BEFORE" ] && [ "$INV_BEFORE" != "" ] && [ "$INV_BEFORE" != "null" ]; then curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_inv_after.json 2>/dev/null - INV_AFTER=$(jq -r '.content[] | select(.productId == 1) | .quantity // 0' /tmp/qa_inv_after.json 2>/dev/null | head -1) + INV_AFTER=$(jq -r '.content[] | select(.prodId == 1) | .quantity // 0' /tmp/qa_inv_after.json 2>/dev/null | head -1) if [ -n "$INV_AFTER" ] && [ "$INV_AFTER" != "null" ] && [ "$((INV_BEFORE - 1))" = "$INV_AFTER" ]; then PASS=$((PASS+1)) else @@ -391,12 +391,12 @@ if [ -n "$INV_BEFORE" ] && [ "$INV_BEFORE" != "" ] && [ "$INV_BEFORE" != "null" fi fi -check "Sale non-existent product" 404 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":999999,"quantity":1}]}' -check "Sale zero quantity" 400 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":0}]}' -check "Sale (no auth)" 401 POST "/sales" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":1,"quantity":1}]}' -check "Sale (customer)" 403 POST "/sales" "${C[@]}" "${J[@]}" -d '{"storeId":1,"customerId":15,"items":[{"productId":1,"quantity":1}]}' +check "Sale non-existent product" 404 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":999999,"quantity":1}]}' +check "Sale zero quantity" 400 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":1,"quantity":0}]}' +check "Sale (no auth)" 401 POST "/sales" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":1,"quantity":1}]}' +check "Sale (customer)" 403 POST "/sales" "${C[@]}" "${J[@]}" -d '{"storeId":1,"customerId":15,"items":[{"prodId":1,"quantity":1}]}' -check "Staff create sale 2" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":2,"quantity":1}],"notes":"QA_TEST sale2"}' +check "Staff create sale 2" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":2,"quantity":1}]}' SALE_ID_2=$(id_from_body) [ -n "$SALE_ID_2" ] && CLEANUP_SALE_IDS+=("$SALE_ID_2") @@ -407,46 +407,46 @@ echo "--- 11. CART ---" check "GET /cart (customer)" 200 GET "/cart?storeId=1" "${C[@]}" -check "Add cart item" 200 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":1}' -if [ "$(jq -r '.status // empty' /tmp/qa_body.json 2>/dev/null)" = "" ]; then - check "Add cart item alt" 201 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":2,"storeId":1,"quantity":1}' +check "Add cart item" 200 POST "/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":1}' +if [ "$(jq -r '.cartStatus // empty' /tmp/qa_body.json 2>/dev/null)" = "" ]; then + check "Add cart item alt" 200 POST "/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":2,"storeId":1,"quantity":1}' fi check "GET cart with item" 200 GET "/cart?storeId=1" "${C[@]}" -CART_SUBTOTAL=$(jq -r '.subtotal // .totalBeforeDiscount // 0' /tmp/qa_body.json 2>/dev/null) -CART_ITEM_ID=$(jq -r '.items[0].id // .items[0].cartItemId // empty' /tmp/qa_body.json 2>/dev/null) +CART_SUBTOTAL=$(jq -r '.subtotalAmount // 0' /tmp/qa_body.json 2>/dev/null) +CART_ITEM_ID=$(jq -r '.items[0].cartItemId // empty' /tmp/qa_body.json 2>/dev/null) if [ -n "$CART_ITEM_ID" ]; then - check "Update cart qty" 200 PUT "/cart/items/$CART_ITEM_ID" "${C[@]}" "${J[@]}" -d '{"quantity":2}' + check "Update cart qty" 200 PUT "/cart/update" "${C[@]}" "${J[@]}" -d "{\"cartItemId\":$CART_ITEM_ID,\"quantity\":2}" check "GET cart after qty update" 200 GET "/cart?storeId=1" "${C[@]}" fi -check "Apply coupon WELCOME10" 200 POST "/cart/coupon" "${C[@]}" "${J[@]}" -d '{"couponCode":"WELCOME10","storeId":1}' +check "Apply coupon WELCOME10" 200 POST "/cart/apply-coupon?storeId=1" "${C[@]}" "${J[@]}" -d '{"couponCode":"WELCOME10"}' check "GET cart with coupon" 200 GET "/cart?storeId=1" "${C[@]}" -DISCOUNT=$(jq -r '.discount // .totalDiscount // 0' /tmp/qa_body.json 2>/dev/null) +DISCOUNT=$(jq -r '.discountAmount // 0' /tmp/qa_body.json 2>/dev/null) -check "Remove coupon" 200 DELETE "/cart/coupon?storeId=1" "${C[@]}" +check "Remove coupon" 200 DELETE "/cart/coupon?storeId=1" "${C[@]}" "${J[@]}" check "GET cart no coupon" 200 GET "/cart?storeId=1" "${C[@]}" -check "Apply points" 200 POST "/cart/points" "${C[@]}" "${J[@]}" -d '{"points":10,"storeId":1}' -check "Remove points" 200 DELETE "/cart/points?storeId=1" "${C[@]}" +check "Apply points" 200 POST "/cart/apply-points?storeId=1&useLoyaltyPoints=true" "${C[@]}" "${J[@]}" +check "Remove points" 200 POST "/cart/apply-points?storeId=1&useLoyaltyPoints=false" "${C[@]}" "${J[@]}" if [ -n "$CART_ITEM_ID" ]; then - check "Remove cart item" 200 DELETE "/cart/items/$CART_ITEM_ID" "${C[@]}" + check "Remove cart item" 200 DELETE "/cart/remove/$CART_ITEM_ID" "${C[@]}" fi -check "Clear cart" 200 DELETE "/cart?storeId=1" "${C[@]}" +check "Clear cart" 204 DELETE "/cart/clear?storeId=1" "${C[@]}" -check "Add item qty 0" 400 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":0}' -check "Apply invalid coupon" 400 POST "/cart/coupon" "${C[@]}" "${J[@]}" -d '{"couponCode":"FAKECOUPON999","storeId":1}' +check "Add item qty 0" 400 POST "/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":0}' +check "Apply invalid coupon" 400 POST "/cart/apply-coupon?storeId=1" "${C[@]}" "${J[@]}" -d '{"couponCode":"FAKECOUPON999"}' check "Checkout empty cart" 400 POST "/cart/checkout" "${C[@]}" "${J[@]}" -d '{"storeId":1}' -check "Add item for checkout test" 200 POST "/cart/items" "${C[@]}" "${J[@]}" -d '{"productId":1,"storeId":1,"quantity":1}' +check "Add item for checkout test" 200 POST "/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":1}' check "Checkout" 200 POST "/cart/checkout" "${C[@]}" "${J[@]}" -d '{"storeId":1}' -CHECKOUT_SALE_ID=$(jq -r '.id // .saleId // empty' /tmp/qa_body.json 2>/dev/null) +CHECKOUT_SALE_ID=$(jq -r '.cartId // empty' /tmp/qa_body.json 2>/dev/null) [ -n "$CHECKOUT_SALE_ID" ] && CLEANUP_SALE_IDS+=("$CHECKOUT_SALE_ID") -check "Clear cart final" 200 DELETE "/cart?storeId=1" "${C[@]}" +check "Clear cart final" 204 DELETE "/cart/clear?storeId=1" "${C[@]}" echo "--- 12. REFUNDS ---" @@ -458,21 +458,21 @@ if [ -n "$CHECKOUT_SALE_ID" ]; then CUST_SALE_ID="$CHECKOUT_SALE_ID" else curl -s "$BASE/sales/my" "${C[@]}" -o /tmp/qa_my_sales.json 2>/dev/null - CUST_SALE_ID=$(jq -r '.content[0].id // .[0].id // empty' /tmp/qa_my_sales.json 2>/dev/null) + CUST_SALE_ID=$(jq -r '.content[0].saleId // .[0].saleId // empty' /tmp/qa_my_sales.json 2>/dev/null) fi if [ -n "$CUST_SALE_ID" ]; then - check "Customer create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CUST_SALE_ID,\"reason\":\"QA_TEST refund reason\"}" + check "Customer create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CUST_SALE_ID,\"reason\":\"QA_TEST refund reason\",\"items\":[{\"prodId\":1,\"quantity\":1}]}" REFUND_ID=$(id_from_body) if [ -n "$REFUND_ID" ]; then - check "Staff approve refund" 200 PUT "/refunds/$REFUND_ID" "${S[@]}" "${J[@]}" -d "{\"status\":\"Approved\",\"saleId\":$CUST_SALE_ID,\"reason\":\"QA_TEST approved\"}" + check "Staff approve refund" 200 PUT "/refunds/$REFUND_ID" "${S[@]}" "${J[@]}" -d '{"status":"Approved"}' check_field "Refund approved" ".status" "Approved" fi fi -check "Refund non-existent sale" 404 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":999999,"reason":"QA_TEST nope"}' -check "Refund missing reason" 400 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":1}' +check "Refund non-existent sale" 404 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":999999,"reason":"QA_TEST nope","items":[{"prodId":1,"quantity":1}]}' +check "Refund missing reason" 400 POST "/refunds" "${C[@]}" "${J[@]}" -d '{"saleId":1,"items":[{"prodId":1,"quantity":1}]}' check "GET /refunds (staff)" 200 GET "/refunds" "${S[@]}" check "GET /refunds paginated" 200 GET "/refunds?page=0&size=5" "${A[@]}" @@ -483,30 +483,30 @@ check "GET /coupons/1" 200 GET "/coupons/1" "${A[@]}" check "GET /coupons/code/WELCOME10" 200 GET "/coupons/code/WELCOME10" "${A[@]}" check "GET /coupons (customer)" 403 GET "/coupons" "${C[@]}" -check "Create coupon" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_CPN","discountPercent":10,"description":"QA test coupon","expiryDate":"2028-12-31","maxUses":100}' +check "Create coupon" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_CPN","discountType":"PERCENTAGE","discountValue":10,"usageLimit":100}' CPN_ID=$(id_from_body) [ -n "$CPN_ID" ] && CLEANUP_COUPON_IDS+=("$CPN_ID") -check_field "Coupon code" ".code" "QA_TEST_CPN" +check_field "Coupon code" ".couponCode" "QA_TEST_CPN" if [ -n "$CPN_ID" ]; then - check "Update coupon" 200 PUT "/coupons/$CPN_ID" "${A[@]}" "${J[@]}" -d "{\"code\":\"QA_TEST_CPN\",\"discountPercent\":15,\"description\":\"updated\",\"expiryDate\":\"2028-12-31\",\"maxUses\":50}" + check "Update coupon" 200 PUT "/coupons/$CPN_ID" "${A[@]}" "${J[@]}" -d "{\"couponCode\":\"QA_TEST_CPN\",\"discountType\":\"PERCENTAGE\",\"discountValue\":15,\"usageLimit\":50}" fi -check "Create coupon to delete" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_DEL","discountPercent":5,"description":"del","expiryDate":"2028-12-31","maxUses":10}' +check "Create coupon to delete" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_DEL","discountType":"PERCENTAGE","discountValue":5,"usageLimit":10}' DEL_CPN_ID=$(id_from_body) if [ -n "$DEL_CPN_ID" ]; then check "Delete coupon" 200 DELETE "/coupons/$DEL_CPN_ID" "${A[@]}" fi -check "Coupon 100% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_100","discountPercent":100,"description":"bad","expiryDate":"2028-12-31","maxUses":1}' -check "Coupon 150% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_150","discountPercent":150,"description":"bad","expiryDate":"2028-12-31","maxUses":1}' +check "Coupon 100% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_100","discountType":"PERCENTAGE","discountValue":100,"usageLimit":1}' +check "Coupon 150% discount" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_150","discountType":"PERCENTAGE","discountValue":150,"usageLimit":1}' -check "Coupon 99.99%" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"QA_TEST_99","discountPercent":99.99,"description":"boundary","expiryDate":"2028-12-31","maxUses":1}' +check "Coupon 99.99%" 201 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_99","discountType":"PERCENTAGE","discountValue":99.99,"usageLimit":1}' BOUNDARY_CPN_ID=$(id_from_body) [ -n "$BOUNDARY_CPN_ID" ] && CLEANUP_COUPON_IDS+=("$BOUNDARY_CPN_ID") -check "Duplicate coupon code" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"code":"WELCOME10","discountPercent":5,"description":"dup","expiryDate":"2028-12-31","maxUses":1}' -check "Create coupon (customer)" 403 POST "/coupons" "${C[@]}" "${J[@]}" -d '{"code":"QA_TEST_NOPE","discountPercent":5,"description":"nope","expiryDate":"2028-12-31","maxUses":1}' +check "Duplicate coupon code" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"WELCOME10","discountType":"PERCENTAGE","discountValue":5,"usageLimit":1}' +check "Create coupon (customer)" 403 POST "/coupons" "${C[@]}" "${J[@]}" -d '{"couponCode":"QA_TEST_NOPE","discountType":"PERCENTAGE","discountValue":5,"usageLimit":1}' echo "--- 14. SUPPLIERS / PRODUCT-SUPPLIERS / INVENTORY / PURCHASE-ORDERS ---" @@ -514,15 +514,15 @@ check "GET /suppliers (admin)" 200 GET "/suppliers" "${A[@]}" check "GET /suppliers/1" 200 GET "/suppliers/1" "${A[@]}" check "GET /suppliers (customer)" 403 GET "/suppliers" "${C[@]}" -check "Create supplier" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supplierName":"QA_TEST_Supplier","contactName":"QA Contact","email":"qa_supplier@test.com","phone":"5553333333","address":"100 Supply Rd"}' +check "Create supplier" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supCompany":"QA_TEST_Supplier","supContactFirstName":"QA","supContactLastName":"Contact","supEmail":"qa_supplier@test.com","supPhone":"5553333333"}' SUPP_ID=$(id_from_body) [ -n "$SUPP_ID" ] && CLEANUP_SUPPLIER_IDS+=("$SUPP_ID") if [ -n "$SUPP_ID" ]; then - check "Update supplier" 200 PUT "/suppliers/$SUPP_ID" "${A[@]}" "${J[@]}" -d "{\"supplierName\":\"QA_TEST_SuppUpd\",\"contactName\":\"QA Updated\",\"email\":\"qa_supp_upd@test.com\",\"phone\":\"5553333334\",\"address\":\"101 Supply Rd\"}" + check "Update supplier" 200 PUT "/suppliers/$SUPP_ID" "${A[@]}" "${J[@]}" -d "{\"supCompany\":\"QA_TEST_SuppUpd\",\"supContactFirstName\":\"QA\",\"supContactLastName\":\"Updated\",\"supEmail\":\"qa_supp_upd@test.com\",\"supPhone\":\"5553333334\"}" fi -check "Create supplier to delete" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supplierName":"QA_TEST_SuppDel","contactName":"Del","email":"del@test.com","phone":"5550000000","address":"x"}' +check "Create supplier to delete" 201 POST "/suppliers" "${A[@]}" "${J[@]}" -d '{"supCompany":"QA_TEST_SuppDel","supContactFirstName":"Del","supContactLastName":"Test","supEmail":"del@test.com","supPhone":"5550000000"}' DEL_SUPP_ID=$(id_from_body) if [ -n "$DEL_SUPP_ID" ]; then check "Delete supplier" 200 DELETE "/suppliers/$DEL_SUPP_ID" "${A[@]}" @@ -551,7 +551,7 @@ if [ -n "$CONV_ID" ]; then check "GET messages" 200 GET "/chat/conversations/$CONV_ID/messages" "${C[@]}" - check "Request human takeover" 200 POST "/chat/conversations/$CONV_ID/takeover" "${C[@]}" "${J[@]}" -d '{}' + check "Request human takeover" 200 POST "/chat/conversations/$CONV_ID/request-human" "${C[@]}" "${J[@]}" -d '{}' check "Staff view conversations" 200 GET "/chat/conversations" "${S[@]}" @@ -559,7 +559,7 @@ if [ -n "$CONV_ID" ]; then check "GET conversation detail" 200 GET "/chat/conversations/$CONV_ID" "${S[@]}" - check "Close conversation" 200 PUT "/chat/conversations/$CONV_ID/close" "${S[@]}" "${J[@]}" -d '{}' + check "Close conversation" 200 PUT "/chat/conversations/$CONV_ID" "${S[@]}" "${J[@]}" -d '{"status":"CLOSED"}' check "GET closed conversation" 200 GET "/chat/conversations/$CONV_ID" "${C[@]}" check_field "Conversation closed" ".status" "CLOSED" @@ -594,15 +594,15 @@ check "GET /dropdowns/customers/32/pets" 200 GET "/dropdowns/customers/32/pets" echo "--- 17. CROSS-ENTITY ---" curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv1.json 2>/dev/null -CROSS_INV_BEFORE=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv1.json 2>/dev/null | head -1) +CROSS_INV_BEFORE=$(jq -r '.content[] | select(.prodId == 2) | .quantity // 0' /tmp/qa_cross_inv1.json 2>/dev/null | head -1) -check "Cross: create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"productId":2,"quantity":1}],"notes":"QA_TEST_CROSS"}' +check "Cross: create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"customerId":32,"items":[{"prodId":2,"quantity":1}]}' CROSS_SALE_ID=$(id_from_body) [ -n "$CROSS_SALE_ID" ] && CLEANUP_SALE_IDS+=("$CROSS_SALE_ID") if [ -n "$CROSS_INV_BEFORE" ] && [ "$CROSS_INV_BEFORE" != "null" ] && [ "$CROSS_INV_BEFORE" != "" ]; then curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv2.json 2>/dev/null - CROSS_INV_AFTER=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv2.json 2>/dev/null | head -1) + CROSS_INV_AFTER=$(jq -r '.content[] | select(.prodId == 2) | .quantity // 0' /tmp/qa_cross_inv2.json 2>/dev/null | head -1) if [ "$((CROSS_INV_BEFORE - 1))" = "$CROSS_INV_AFTER" ]; then PASS=$((PASS+1)) else @@ -611,12 +611,12 @@ if [ -n "$CROSS_INV_BEFORE" ] && [ "$CROSS_INV_BEFORE" != "null" ] && [ "$CROSS_ fi if [ -n "$CROSS_SALE_ID" ]; then - check "Cross: create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CROSS_SALE_ID,\"reason\":\"QA_TEST cross refund\"}" + check "Cross: create refund" 201 POST "/refunds" "${C[@]}" "${J[@]}" -d "{\"saleId\":$CROSS_SALE_ID,\"reason\":\"QA_TEST cross refund\",\"items\":[{\"prodId\":2,\"quantity\":1}]}" CROSS_REFUND_ID=$(id_from_body) if [ -n "$CROSS_REFUND_ID" ]; then - check "Cross: approve refund" 200 PUT "/refunds/$CROSS_REFUND_ID" "${S[@]}" "${J[@]}" -d "{\"status\":\"Approved\",\"saleId\":$CROSS_SALE_ID,\"reason\":\"QA_TEST cross approved\"}" + check "Cross: approve refund" 200 PUT "/refunds/$CROSS_REFUND_ID" "${S[@]}" "${J[@]}" -d '{"status":"Approved"}' curl -s "$BASE/inventory?storeId=1" "${A[@]}" -o /tmp/qa_cross_inv3.json 2>/dev/null - CROSS_INV_RESTORED=$(jq -r '.content[] | select(.productId == 2) | .quantity // 0' /tmp/qa_cross_inv3.json 2>/dev/null | head -1) + CROSS_INV_RESTORED=$(jq -r '.content[] | select(.prodId == 2) | .quantity // 0' /tmp/qa_cross_inv3.json 2>/dev/null | head -1) if [ -n "$CROSS_INV_RESTORED" ] && [ "$CROSS_INV_RESTORED" = "$CROSS_INV_BEFORE" ]; then PASS=$((PASS+1)) else @@ -626,35 +626,35 @@ if [ -n "$CROSS_SALE_ID" ]; then fi curl -s "$BASE/pets?status=Available&size=5&page=1" "${A[@]}" -o /tmp/qa_cross_avail.json 2>/dev/null -CROSS_PET=$(jq -r '.content[0].id // empty' /tmp/qa_cross_avail.json 2>/dev/null) +CROSS_PET=$(jq -r '.content[0].petId // empty' /tmp/qa_cross_avail.json 2>/dev/null) if [ -n "$CROSS_PET" ]; then - check "Cross: create adoption Pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST cross adopt\"}" + check "Cross: create adoption Pending" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" CROSS_ADOPT_ID=$(id_from_body) [ -n "$CROSS_ADOPT_ID" ] && CLEANUP_ADOPTION_IDS+=("$CROSS_ADOPT_ID") check "Cross: pet is Pending" 200 GET "/pets/$CROSS_PET" - check_field "Cross pet Pending" ".status" "Pending" + check_field "Cross pet Pending" ".petStatus" "Pending" if [ -n "$CROSS_ADOPT_ID" ]; then - check "Cross: complete adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"storeId\":1,\"status\":\"Completed\",\"notes\":\"QA_TEST cross completed\"}" + check "Cross: complete adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET,\"customerId\":35,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Completed\"}" check "Cross: pet is Adopted" 200 GET "/pets/$CROSS_PET" - check_field "Cross pet Adopted" ".status" "Adopted" + check_field "Cross pet Adopted" ".petStatus" "Adopted" fi fi curl -s "$BASE/pets?status=Available&size=5&page=2" "${A[@]}" -o /tmp/qa_cross_avail2.json 2>/dev/null -CROSS_PET_2=$(jq -r '.content[0].id // empty' /tmp/qa_cross_avail2.json 2>/dev/null) +CROSS_PET_2=$(jq -r '.content[0].petId // empty' /tmp/qa_cross_avail2.json 2>/dev/null) if [ -n "$CROSS_PET_2" ]; then - check "Cross: adopt then cancel" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"storeId\":1,\"status\":\"Pending\",\"notes\":\"QA_TEST cross cancel\"}" + check "Cross: adopt then cancel" 201 POST "/adoptions" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Pending\"}" CROSS_ADOPT_ID_2=$(id_from_body) [ -n "$CROSS_ADOPT_ID_2" ] && CLEANUP_ADOPTION_IDS+=("$CROSS_ADOPT_ID_2") if [ -n "$CROSS_ADOPT_ID_2" ]; then - check "Cross: cancel adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"storeId\":1,\"status\":\"Cancelled\",\"notes\":\"QA_TEST cross cancelled\"}" + check "Cross: cancel adoption" 200 PUT "/adoptions/$CROSS_ADOPT_ID_2" "${S[@]}" "${J[@]}" -d "{\"petId\":$CROSS_PET_2,\"customerId\":36,\"sourceStoreId\":1,\"adoptionDate\":\"2027-06-15\",\"adoptionStatus\":\"Cancelled\"}" check "Cross: pet Available" 200 GET "/pets/$CROSS_PET_2" - check_field "Cross pet Available" ".status" "Available" + check_field "Cross pet Available" ".petStatus" "Available" fi fi @@ -662,7 +662,7 @@ echo "" echo "--- CLEANUP ---" for id in "${CLEANUP_CONV_IDS[@]}"; do - curl -s -o /dev/null -X PUT "$BASE/chat/conversations/$id/close" "${S[@]}" "${J[@]}" -d '{}' 2>/dev/null + curl -s -o /dev/null -X PUT "$BASE/chat/conversations/$id" "${S[@]}" "${J[@]}" -d '{"status":"CLOSED"}' 2>/dev/null done for id in "${CLEANUP_ADOPTION_IDS[@]}"; do -- 2.49.1 From 8873ef5bef9a10d39d0ba6ed06a6c6a3713511f9 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 07:55:17 -0600 Subject: [PATCH 21/42] add CORRECT test script --- test-backend-correct.sh | 512 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100755 test-backend-correct.sh diff --git a/test-backend-correct.sh b/test-backend-correct.sh new file mode 100755 index 00000000..406638a3 --- /dev/null +++ b/test-backend-correct.sh @@ -0,0 +1,512 @@ +#!/usr/bin/env bash +set -uo pipefail + +BASE="https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io/api/v1" +PASS=0 FAIL=0 +CLEANUP_PRODUCT_IDS=() +CLEANUP_SALE_IDS=() +CLEANUP_COUPON_IDS=() + +check() { + local label="$1" expect="$2" method="$3" path="$4"; shift 4 + local code=$(curl -s -o /tmp/qa_body.json -w "%{http_code}" -X "$method" "$BASE$path" "$@" 2>/dev/null) + if [ "$code" = "$expect" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — expected $expect got $code"; fi +} +check_field() { + local actual=$(jq -r "$2" /tmp/qa_body.json 2>/dev/null) + if [ "$actual" = "$3" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $1 — expected '$3' got '$actual'"; fi +} +check_not_500() { + local label="$1" method="$2" path="$3"; shift 3 + local code=$(curl -s -o /tmp/qa_body.json -w "%{http_code}" -X "$method" "$BASE$path" "$@" 2>/dev/null) + if [ "$code" != "500" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label — got 500 Internal Server Error"; fi +} +check_true() { + local label="$1" expr="$2" + local result=$(jq "$expr" /tmp/qa_body.json 2>/dev/null) + if [ "$result" = "true" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: $label"; fi +} + +echo "=== Logging in ===" +ADMIN_TOKEN=$(curl -s -X POST "$BASE/auth/login" -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' | jq -r '.token') +STAFF_TOKEN=$(curl -s -X POST "$BASE/auth/login" -H 'Content-Type: application/json' -d '{"username":"staff","password":"staff123"}' | jq -r '.token') +CUST_TOKEN=$(curl -s -X POST "$BASE/auth/login" -H 'Content-Type: application/json' -d '{"username":"customer","password":"customer123"}' | jq -r '.token') + +if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then echo "FATAL: Admin login failed"; exit 1; fi +if [ "$STAFF_TOKEN" = "null" ] || [ -z "$STAFF_TOKEN" ]; then echo "FATAL: Staff login failed"; exit 1; fi +if [ "$CUST_TOKEN" = "null" ] || [ -z "$CUST_TOKEN" ]; then echo "FATAL: Customer login failed"; exit 1; fi +echo "All 3 logins OK" + +A=(-H "Authorization: Bearer $ADMIN_TOKEN") +S=(-H "Authorization: Bearer $STAFF_TOKEN") +C=(-H "Authorization: Bearer $CUST_TOKEN") +J=(-H "Content-Type: application/json") + +cleanup() { + echo "" + echo "=== Cleanup ===" + for pid in "${CLEANUP_PRODUCT_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/products/$pid" "${A[@]}" 2>/dev/null + done + for sid in "${CLEANUP_SALE_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/sales/$sid" "${A[@]}" 2>/dev/null + done + for cid in "${CLEANUP_COUPON_IDS[@]}"; do + curl -s -o /dev/null -X DELETE "$BASE/coupons/$cid" "${A[@]}" 2>/dev/null + done + echo "Cleanup done" +} +trap cleanup EXIT + +############################################################################### +echo "" +echo "========== C — CONFORMANCE ==========" +############################################################################### + +echo "--- Response format validation ---" +check "GET /products 200" 200 GET "/products" +check_true "products .content is array" '.content | type == "array"' +check_true "products has totalElements" '.totalElements != null' +check_true "products has totalPages" '.totalPages != null' +check_true "products has number" '.number != null' +check_true "products has size" '.size != null' + +check "GET /products/1 200" 200 GET "/products/1" +check_true "prodPrice is number ≤2 decimals" '(.prodPrice | tostring | test("^[0-9]+(\\.[0-9]{1,2})?$"))' + +check "GET /sales admin 200" 200 GET "/sales" "${A[@]}" "${J[@]}" +check_true "saleDate has ISO T" '(.content[0].saleDate | test("T"))' + +check "GET /appointments admin 200" 200 GET "/appointments" "${A[@]}" "${J[@]}" +check_true "appointmentDate YYYY-MM-DD" '(.content[0].appointmentDate | test("^[0-9]{4}-[0-9]{2}-[0-9]{2}"))' + +local_code=$(curl -s -o /tmp/qa_body.json -w "%{http_code}" -D /tmp/qa_headers.txt -X GET "$BASE/auth/me" "${A[@]}" 2>/dev/null) +if grep -qi "application/json" /tmp/qa_headers.txt 2>/dev/null; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: /auth/me Content-Type not json"; fi + +local_code=$(curl -s -o /tmp/qa_body.json -w "%{http_code}" -D /tmp/qa_headers.txt -X GET "$BASE/users/1/avatar/file" "${A[@]}" 2>/dev/null) +if grep -qi "image/" /tmp/qa_headers.txt 2>/dev/null; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: avatar Content-Type not image/"; fi + +echo "--- Status enum validation ---" +check "GET /appointments enum" 200 GET "/appointments?size=50" "${A[@]}" +check_true "appointmentStatus valid" '[.content[].appointmentStatus] | all(. == "Scheduled" or . == "Completed" or . == "Cancelled" or . == "In Progress" or . == "No Show")' + +check "GET /pets enum" 200 GET "/pets?size=100" +check_true "petStatus valid" '[.content[].petStatus] | all(. == "Available" or . == "Pending" or . == "Adopted" or . == "Owned")' + +check "GET /sales enum" 200 GET "/sales?size=50" "${A[@]}" +check_true "channel valid" '[.content[].channel] | all(. == "IN_STORE" or . == "ONLINE")' + +check "GET /users enum" 200 GET "/users?size=100" "${A[@]}" +check_true "role valid" '[.content[].role] | all(. == "ADMIN" or . == "STAFF" or . == "CUSTOMER")' + +check "GET /coupons enum" 200 GET "/coupons" "${A[@]}" +check_true "discountType valid" '[.[].discountType] | all(. == "PERCENTAGE" or . == "FIXED")' + +echo "--- Error format validation ---" +check "GET /products/999999 404" 404 GET "/products/999999" +check_true "404 has status" '.status != null' +check_true "404 has message" '.message != null' +check_true "404 has path" '.path != null' +check_true "404 has timestamp" '.timestamp != null' + +echo "--- Input format enforcement ---" +check_not_500 "invalid JSON body" POST "/products" "${A[@]}" "${J[@]}" -d '{bad json}' +check_not_500 "text/plain content-type" POST "/products" "${A[@]}" -H "Content-Type: text/plain" -d 'hello' +check_not_500 "empty body" POST "/products" "${A[@]}" "${J[@]}" -d '' +check_not_500 "XML body" POST "/products" "${A[@]}" -H "Content-Type: application/xml" -d '' + +echo "--- Security conformance ---" +check_not_500 "SQL injection in search" GET "/products?q=%27%20OR%201%3D1--" +check_not_500 "XSS in search" GET '/products?q=' + +check_not_500 "XSS in prodName" POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"","prodPrice":9.99,"categoryId":1}' +XSS_ID=$(jq -r '.prodId // empty' /tmp/qa_body.json 2>/dev/null) +if [ -n "$XSS_ID" ]; then + CLEANUP_PRODUCT_IDS+=("$XSS_ID") + check "GET XSS product" 200 GET "/products/$XSS_ID" + check_true "XSS stored literally" '(.prodName | contains("/dev/null) +if [ -n "$UNI_ID" ]; then + CLEANUP_PRODUCT_IDS+=("$UNI_ID") + check "GET unicode product" 200 GET "/products/$UNI_ID" + check_field "unicode preserved" '.prodName' 'Café Résumé' +fi + +check_not_500 "null byte in category" POST "/categories" "${A[@]}" "${J[@]}" -d '{"categoryName":"test\u0000inject"}' + +############################################################################### +echo "" +echo "========== O — ORDERING ==========" +############################################################################### + +check "products price asc" 200 GET "/products?sort=prodPrice,asc&size=100" +check_true "prices non-decreasing" '[.content[].prodPrice] | . as $a | [range(1;length)] | all(. as $i | $a[$i] >= $a[$i-1])' + +check "products price desc" 200 GET "/products?sort=prodPrice,desc&size=100" +check_true "prices non-increasing" '[.content[].prodPrice] | . as $a | [range(1;length)] | all(. as $i | $a[$i] <= $a[$i-1])' + +check "products name asc" 200 GET "/products?sort=prodName,asc&size=100" +check_true "names alphabetical" '[.content[].prodName] | . as $a | [range(1;length)] | all(. as $i | $a[$i] >= $a[$i-1])' + +check "sales date desc" 200 GET "/sales?sort=saleDate,desc&size=50" "${A[@]}" +check_true "sale dates non-increasing" '[.content[].saleDate] | . as $a | [range(1;length)] | all(. as $i | $a[$i] <= $a[$i-1])' + +check "users lastName asc" 200 GET "/users?sort=lastName,asc&size=100" "${A[@]}" +check_true "lastNames alphabetical" '[.content[].lastName] | . as $a | [range(1;length)] | all(. as $i | $a[$i] >= $a[$i-1])' + +check_not_500 "sort by nonexistent field" GET "/products?sort=nonexistent,asc" + +check "page 0 size 5" 200 GET "/products?page=0&size=5" +PAGE0_IDS=$(jq -r '[.content[].prodId] | join(",")' /tmp/qa_body.json) +check "page 1 size 5" 200 GET "/products?page=1&size=5" +PAGE1_IDS=$(jq -r '[.content[].prodId] | join(",")' /tmp/qa_body.json) +OVERLAP=0 +IFS=',' read -ra P0 <<< "$PAGE0_IDS" +IFS=',' read -ra P1 <<< "$PAGE1_IDS" +for a in "${P0[@]}"; do for b in "${P1[@]}"; do [ "$a" = "$b" ] && OVERLAP=1; done; done +if [ "$OVERLAP" = "0" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: page 0 and page 1 overlap"; fi + +check "conversations" 200 GET "/chat/conversations" "${C[@]}" +CONV_ID=$(jq -r '.[0].conversationId // empty' /tmp/qa_body.json 2>/dev/null) +if [ -n "$CONV_ID" ]; then + check "conv messages" 200 GET "/chat/conversations/$CONV_ID/messages" "${C[@]}" + check_true "message timestamps ascending" '. as $a | [range(1;length)] | all(. as $i | $a[$i].timestamp >= $a[$i-1].timestamp)' +fi + +check "activity logs desc" 200 GET "/activity-logs?limit=10" "${A[@]}" +check_true "log timestamps descending" '. as $a | [range(1;length)] | all(. as $i | $a[$i].logTimestamp <= $a[$i-1].logTimestamp)' + +check "availability times" 200 GET "/appointments/availability?storeId=1&serviceId=1&date=2027-06-01" "${A[@]}" +check_true "times ascending" '. as $a | [range(1;length)] | all(. as $i | $a[$i] >= $a[$i-1])' + +############################################################################### +echo "" +echo "========== R — RANGE ==========" +############################################################################### + +echo "--- Data range checks ---" +check "products range" 200 GET "/products?size=200" +check_true "all prodPrice > 0" '[.content[].prodPrice] | all(. > 0)' + +check "services range" 200 GET "/services" +check_true "all servicePrice > 0" '[.[].servicePrice] | all(. > 0)' +check_true "all serviceDuration > 0" '[.[].serviceDuration] | all(. > 0)' + +check "inventory range" 200 GET "/inventory" "${A[@]}" +check_true "all quantity >= 0" '[.content[].quantity] | all(. >= 0)' + +check "users loyalty" 200 GET "/users?size=100" "${A[@]}" +check_true "all loyaltyPoints >= 0" '[.content[].loyaltyPoints] | all(. >= 0)' + +check "coupons range" 200 GET "/coupons" "${A[@]}" +check_true "PERCENTAGE < 100 and > 0" '[.[] | select(.discountType == "PERCENTAGE") | .discountValue] | all(. > 0 and . < 100)' + +check "pets age range" 200 GET "/pets?size=200" +check_true "all petAge >= 0" '[.content[].petAge | select(. != null)] | all(. >= 0)' + +echo "--- Input range enforcement ---" +check "negative price → 400" 400 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_NEG","prodPrice":-1,"categoryId":1}' +NEG_ID=$(jq -r '.prodId // empty' /tmp/qa_body.json 2>/dev/null) +[ -n "$NEG_ID" ] && CLEANUP_PRODUCT_IDS+=("$NEG_ID") + +check "zero price → 400" 400 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_ZERO","prodPrice":0,"categoryId":1}' +Z_ID=$(jq -r '.prodId // empty' /tmp/qa_body.json 2>/dev/null) +[ -n "$Z_ID" ] && CLEANUP_PRODUCT_IDS+=("$Z_ID") + +check "zero duration → 400" 400 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_DUR0","servicePrice":10,"serviceDuration":0}' +check "neg duration → 400" 400 POST "/services" "${A[@]}" "${J[@]}" -d '{"serviceName":"QA_DURN","servicePrice":10,"serviceDuration":-5}' + +check "neg cart qty → 400" 400 POST "/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":-1}' + +check "zero coupon pct → 400" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"QA_ZERO_PCT","discountType":"PERCENTAGE","discountValue":0}' + +check_not_500 "huge price" POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_HUGE","prodPrice":99999999.99,"categoryId":1}' +HUGE_ID=$(jq -r '.prodId // empty' /tmp/qa_body.json 2>/dev/null) +[ -n "$HUGE_ID" ] && CLEANUP_PRODUCT_IDS+=("$HUGE_ID") + +echo "--- Extreme pagination ---" +check_not_500 "page=-1" GET "/products?page=-1" +check_not_500 "size=0" GET "/products?size=0" +check_not_500 "size=10000" GET "/products?size=10000" + +echo "--- String length ---" +LONG_USER=$(printf 'u%.0s' $(seq 1 60)) +check "60-char username → 400" 400 POST "/auth/register" "${J[@]}" -d "{\"username\":\"$LONG_USER\",\"password\":\"pass1234\",\"firstName\":\"Q\",\"lastName\":\"A\",\"email\":\"${LONG_USER}@test.com\"}" + +LONG_PNAME=$(printf 'p%.0s' $(seq 1 200)) +check "200-char prodName → 400" 400 POST "/products" "${A[@]}" "${J[@]}" -d "{\"prodName\":\"$LONG_PNAME\",\"prodPrice\":9.99,\"categoryId\":1}" + +############################################################################### +echo "" +echo "========== R — REFERENCE ==========" +############################################################################### + +echo "--- FK integrity: sales ---" +check "sales sample" 200 GET "/sales?size=3" "${A[@]}" +for i in 0 1 2; do + SID=$(jq -r ".content[$i].storeId // empty" /tmp/qa_body.json) + EID=$(jq -r ".content[$i].employeeId // empty" /tmp/qa_body.json) + [ -n "$SID" ] && check "sale[$i] store $SID exists" 200 GET "/stores/$SID" + [ -n "$EID" ] && check "sale[$i] employee $EID exists" 200 GET "/users/$EID" "${A[@]}" +done + +echo "--- FK integrity: appointments ---" +check "appts sample" 200 GET "/appointments?size=3" "${A[@]}" +cp /tmp/qa_body.json /tmp/qa_appts.json +for i in 0 1 2; do + SVID=$(jq -r ".content[$i].serviceId // empty" /tmp/qa_appts.json) + STID=$(jq -r ".content[$i].storeId // empty" /tmp/qa_appts.json) + CID=$(jq -r ".content[$i].customerId // empty" /tmp/qa_appts.json) + EMID=$(jq -r ".content[$i].employeeId // empty" /tmp/qa_appts.json) + [ -n "$SVID" ] && check "appt[$i] service $SVID" 200 GET "/services/$SVID" + [ -n "$STID" ] && check "appt[$i] store $STID" 200 GET "/stores/$STID" + [ -n "$CID" ] && check "appt[$i] customer $CID" 200 GET "/users/$CID" "${A[@]}" + [ -n "$EMID" ] && check "appt[$i] employee $EMID" 200 GET "/users/$EMID" "${A[@]}" +done + +echo "--- FK integrity: inventory ---" +check "inv sample" 200 GET "/inventory?size=3" "${A[@]}" +cp /tmp/qa_body.json /tmp/qa_inv.json +for i in 0 1 2; do + ISTID=$(jq -r ".content[$i].storeId // empty" /tmp/qa_inv.json) + IPID=$(jq -r ".content[$i].prodId // empty" /tmp/qa_inv.json) + [ -n "$ISTID" ] && check "inv[$i] store $ISTID" 200 GET "/stores/$ISTID" + [ -n "$IPID" ] && check "inv[$i] product $IPID" 200 GET "/products/$IPID" +done + +echo "--- FK integrity: products ---" +check "products sample" 200 GET "/products?size=3" +cp /tmp/qa_body.json /tmp/qa_prods.json +for i in 0 1 2; do + CATID=$(jq -r ".content[$i].categoryId // empty" /tmp/qa_prods.json) + [ -n "$CATID" ] && check "prod[$i] category $CATID" 200 GET "/categories/$CATID" +done + +echo "--- Cross-ref violations ---" +check "sale bad storeId" 404 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":999,"paymentMethod":"Cash","channel":"IN_STORE","items":[{"prodId":1,"quantity":1}]}' +check "appt bad serviceId" 404 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"serviceId":999,"petId":53,"customerId":32,"storeId":2,"employeeId":7,"appointmentDate":"2027-09-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' +check "appt bad customerId" 404 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"serviceId":1,"petId":53,"customerId":999,"storeId":2,"employeeId":7,"appointmentDate":"2027-09-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' +check "appt bad employeeId" 404 POST "/appointments" "${A[@]}" "${J[@]}" -d '{"serviceId":1,"petId":53,"customerId":32,"storeId":2,"employeeId":999,"appointmentDate":"2027-09-01","appointmentTime":"10:00","appointmentStatus":"Scheduled"}' + +echo "--- Dangling reference (FK on delete) ---" +DEL_CAT_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE/categories/1" "${A[@]}" 2>/dev/null) +if [ "$DEL_CAT_CODE" != "204" ] && [ "$DEL_CAT_CODE" != "200" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: DELETE /categories/1 should fail (FK) — got $DEL_CAT_CODE"; fi + +DEL_STORE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE/stores/1" "${A[@]}" 2>/dev/null) +if [ "$DEL_STORE_CODE" != "204" ] && [ "$DEL_STORE_CODE" != "200" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: DELETE /stores/1 should fail (FK) — got $DEL_STORE_CODE"; fi + +echo "--- Avatar file references ---" +check "admin avatar file" 200 GET "/users/1/avatar/file" "${A[@]}" +check "user 16 avatar file" 200 GET "/users/16/avatar/file" "${A[@]}" +check "product 1 image" 200 GET "/products/1/image" + +############################################################################### +echo "" +echo "========== E — EXISTENCE ==========" +############################################################################### + +echo "--- Required fields never null ---" +check "products fields" 200 GET "/products?size=100" +check_true "products non-null fields" '.content | all(.prodId != null and .prodName != null and .prodPrice != null and .categoryId != null)' + +check "users fields" 200 GET "/users?size=100" "${A[@]}" +check_true "users non-null fields" '.content | all(.id != null and .firstName != null and .lastName != null and .role != null)' + +check "sales fields" 200 GET "/sales?size=50" "${A[@]}" +check_true "sales non-null fields" '.content | all(.saleId != null and .saleDate != null and .totalAmount != null and .storeId != null and .employeeId != null)' + +check "appts fields" 200 GET "/appointments?size=50" "${A[@]}" +check_true "appts non-null fields" '.content | all(.appointmentId != null and .serviceId != null and .customerId != null and .storeId != null and .appointmentDate != null and .appointmentTime != null and .appointmentStatus != null)' + +check "coupons fields" 200 GET "/coupons" "${A[@]}" +check_true "coupons non-null fields" '[.[] | .couponId != null and .couponCode != null and .discountType != null and .discountValue != null] | all' + +echo "--- Avatar existence for all roles ---" +check "admin me avatar" 200 GET "/auth/me/avatar/file" "${A[@]}" +check "staff me avatar" 200 GET "/auth/me/avatar/file" "${S[@]}" +check "customer me avatar" 200 GET "/auth/me/avatar/file" "${C[@]}" + +echo "--- Empty search returns valid structure ---" +check "empty product search" 200 GET "/products?q=zzzznothing" +check_true "empty search .content is array" '.content | type == "array"' +check_true "empty search totalElements 0" '.totalElements == 0' + +check "empty pet species" 200 GET "/pets?species=Unicorn" +check_true "unicorn totalElements 0" '.totalElements == 0' + +check "future appts" 200 GET "/appointments?date=2099-12-31" "${A[@]}" +check_true "far future totalElements 0" '.totalElements == 0' + +check "nonexistent activity log" 200 GET "/activity-logs?search=xyznonexistent" "${A[@]}" +check_true "activity log empty array" 'length == 0' + +echo "--- Missing parameter handling ---" +check_not_500 "cart no storeId" GET "/cart" "${C[@]}" +check_not_500 "availability no params" GET "/appointments/availability" +check_not_500 "checkout empty body" POST "/cart/checkout" "${C[@]}" "${J[@]}" -d '{}' + +############################################################################### +echo "" +echo "========== C — CARDINALITY ==========" +############################################################################### + +echo "--- Unique constraints ---" +check "dup username admin" 409 POST "/auth/register" "${J[@]}" -d '{"username":"admin","password":"pass1234","firstName":"Q","lastName":"A","email":"qadup1@test.com"}' +DUP_CODE=$(cat /tmp/qa_body.json | jq -r '.status // empty' 2>/dev/null) +if [ "$?" != "0" ] || [ "$(curl -s -o /dev/null -w "%{http_code}" /dev/null 2>/dev/null)" = "" ]; then true; fi + +check "dup email admin" 409 POST "/auth/register" "${J[@]}" -d '{"username":"qauniq99","password":"pass1234","firstName":"Q","lastName":"A","email":"admin@petshop.com"}' + +check "dup coupon code" 400 POST "/coupons" "${A[@]}" "${J[@]}" -d '{"couponCode":"WELCOME10","discountType":"PERCENTAGE","discountValue":10}' + +echo "--- One-to-many counts ---" +check "sale 1 items" 200 GET "/sales/1" "${A[@]}" +check_true "sale has >= 1 item" '(.items | length) >= 1' + +check "service 1 species" 200 GET "/services/1" +check_true "service has >= 1 species" '(.species | length) >= 1' + +check "products page size 5" 200 GET "/products?size=5" +check_true "content length <= 5" '(.content | length) <= 5' +check_true "content length = min(5, total)" '(.content | length) == (if .totalElements < 5 then .totalElements else 5 end)' + +echo "--- Pagination math ---" +check "pagination check" 200 GET "/products?page=0&size=10" +check_field "page number is 0" '.number' '0' +check_field "page size is 10" '.size' '10' + +check "products default page" 200 GET "/products" +TOTAL=$(jq -r '.totalElements' /tmp/qa_body.json) +SIZE=$(jq -r '.size' /tmp/qa_body.json) +PAGES=$(jq -r '.totalPages' /tmp/qa_body.json) +EXPECTED_PAGES=$(( (TOTAL + SIZE - 1) / SIZE )) +if [ "$PAGES" = "$EXPECTED_PAGES" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: totalPages=$PAGES expected=$EXPECTED_PAGES (total=$TOTAL size=$SIZE)"; fi + +check "activity logs limit 3" 200 GET "/activity-logs?limit=3" "${A[@]}" +check_true "logs length <= 3" 'length <= 3' + +echo "--- Cart cardinality ---" +curl -s -o /dev/null -X DELETE "$BASE/cart" "${C[@]}" 2>/dev/null +curl -s -o /dev/null -X POST "$BASE/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":1}' 2>/dev/null +check "cart after add 1" 200 GET "/cart?storeId=1" "${C[@]}" +CART_LEN1=$(jq '.items | length' /tmp/qa_body.json 2>/dev/null) + +curl -s -o /dev/null -X POST "$BASE/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":2,"storeId":1,"quantity":1}' 2>/dev/null +check "cart after add 2" 200 GET "/cart?storeId=1" "${C[@]}" +CART_LEN2=$(jq '.items | length' /tmp/qa_body.json 2>/dev/null) + +curl -s -o /dev/null -X POST "$BASE/cart/add" "${C[@]}" "${J[@]}" -d '{"prodId":1,"storeId":1,"quantity":1}' 2>/dev/null +check "cart after re-add 1" 200 GET "/cart?storeId=1" "${C[@]}" +CART_LEN3=$(jq '.items | length' /tmp/qa_body.json 2>/dev/null) + +if [ "$CART_LEN2" = "2" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: cart should have 2 items after adding 2 products, got $CART_LEN2"; fi +if [ "$CART_LEN3" = "2" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: cart should still have 2 items (qty merged), got $CART_LEN3"; fi + +curl -s -o /dev/null -X DELETE "$BASE/cart" "${C[@]}" 2>/dev/null + +echo "--- Availability cardinality ---" +check "avail no dups" 200 GET "/appointments/availability?storeId=1&serviceId=1&date=2027-09-01" "${A[@]}" +check_true "no duplicate times" '[. | group_by(.) | all(length == 1)]' + +############################################################################### +echo "" +echo "========== T — TIME ==========" +############################################################################### + +echo "--- Timestamp freshness ---" +NOW_EPOCH=$(date -u +%s) +check "create product" 201 POST "/products" "${A[@]}" "${J[@]}" -d '{"prodName":"QA_TIME_TEST","prodPrice":5.55,"categoryId":1}' +TPROD_ID=$(jq -r '.prodId // empty' /tmp/qa_body.json) +[ -n "$TPROD_ID" ] && CLEANUP_PRODUCT_IDS+=("$TPROD_ID") + +if [ -n "$TPROD_ID" ]; then + check "get created product" 200 GET "/products/$TPROD_ID" + CREATED_AT=$(jq -r '.createdAt // empty' /tmp/qa_body.json) + if [ -n "$CREATED_AT" ]; then + CREATED_EPOCH=$(date -u -d "$CREATED_AT" +%s 2>/dev/null || echo "0") + DIFF=$((NOW_EPOCH - CREATED_EPOCH)) + if [ "$DIFF" -ge "-10" ] && [ "$DIFF" -le "120" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: createdAt not recent (diff=${DIFF}s)"; fi + else + FAIL=$((FAIL+1)); echo "FAIL: createdAt is null" + fi + + check "update product" 200 PUT "/products/$TPROD_ID" "${A[@]}" "${J[@]}" -d "{\"prodName\":\"QA_TIME_UPD\",\"prodPrice\":6.66,\"categoryId\":1}" + check "get updated product" 200 GET "/products/$TPROD_ID" + check_true "updatedAt >= createdAt" '(.updatedAt // .createdAt) >= .createdAt' +fi + +check "create sale" 201 POST "/sales" "${S[@]}" "${J[@]}" -d '{"storeId":1,"paymentMethod":"Cash","channel":"IN_STORE","items":[{"prodId":2,"quantity":1}]}' +TSALE_ID=$(jq -r '.saleId // empty' /tmp/qa_body.json) +[ -n "$TSALE_ID" ] && CLEANUP_SALE_IDS+=("$TSALE_ID") +if [ -n "$TSALE_ID" ]; then + check "get sale" 200 GET "/sales/$TSALE_ID" "${A[@]}" + check_true "saleDate has T (recent)" '.saleDate | test("T")' +fi + +check "conversations for chat" 200 GET "/chat/conversations" "${C[@]}" +CHAT_CONV=$(jq -r '.[0].conversationId // empty' /tmp/qa_body.json) +if [ -n "$CHAT_CONV" ]; then + curl -s -o /dev/null -X POST "$BASE/chat/conversations/$CHAT_CONV/messages" "${C[@]}" "${J[@]}" -d '{"content":"QA time test message"}' 2>/dev/null + check "get chat messages" 200 GET "/chat/conversations/$CHAT_CONV/messages" "${C[@]}" + LAST_TS=$(jq -r '.[-1].timestamp // empty' /tmp/qa_body.json) + if [ -n "$LAST_TS" ]; then + MSG_EPOCH=$(date -u -d "$LAST_TS" +%s 2>/dev/null || echo "0") + MDIFF=$((NOW_EPOCH - MSG_EPOCH)) + if [ "$MDIFF" -ge "-10" ] && [ "$MDIFF" -le "300" ]; then PASS=$((PASS+1)) + else FAIL=$((FAIL+1)); echo "FAIL: chat message timestamp not recent (diff=${MDIFF}s)"; fi + fi +fi + +echo "--- Temporal constraints ---" +TODAY=$(date -u +%Y-%m-%d) +check "scheduled appts" 200 GET "/appointments?status=Scheduled&size=50" "${A[@]}" +check_true "scheduled dates >= today" "[.content[].appointmentDate] | all(. >= \"$TODAY\")" + +check "coupons temporal" 200 GET "/coupons" "${A[@]}" +NOW_ISO=$(date -u +%Y-%m-%dT%H:%M:%S) +check_true "active coupons endsAt valid" "[.[] | select(.endsAt != null) | .endsAt] | all(. >= \"$NOW_ISO\" or . == null)" + +echo "--- Sequencing ---" +if [ -n "${CHAT_CONV:-}" ]; then + check "chat sequence" 200 GET "/chat/conversations/$CHAT_CONV/messages" "${C[@]}" + check_true "chat timestamps non-decreasing" '. as $a | [range(1;length)] | all(. as $i | $a[$i].timestamp >= $a[$i-1].timestamp)' +fi + +check "activity log sequence" 200 GET "/activity-logs?limit=20" "${A[@]}" +check_true "logs non-increasing" '. as $a | [range(1;length)] | all(. as $i | $a[$i].logTimestamp <= $a[$i-1].logTimestamp)' + +echo "--- Token timing ---" +FRESH_TOKEN=$(curl -s -X POST "$BASE/auth/login" -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' | jq -r '.token') +check "fresh token works" 200 GET "/auth/me" -H "Authorization: Bearer $FRESH_TOKEN" + +curl -s -o /dev/null -X POST "$BASE/auth/logout" -H "Authorization: Bearer $FRESH_TOKEN" 2>/dev/null +LOGOUT_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$BASE/auth/me" -H "Authorization: Bearer $FRESH_TOKEN" 2>/dev/null) +if [ "$LOGOUT_CODE" = "401" ]; then PASS=$((PASS+1)) +else FAIL=$((FAIL+1)); echo "FAIL: old token after logout — expected 401 got $LOGOUT_CODE"; fi + +ADMIN_TOKEN=$(curl -s -X POST "$BASE/auth/login" -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' | jq -r '.token') +A=(-H "Authorization: Bearer $ADMIN_TOKEN") + +echo "" +echo "=========================================" +echo "CORRECT RESULTS: $PASS passed, $FAIL failed" +echo "=========================================" -- 2.49.1 From b6378c30490bc83ecba16f409112620366979c0e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 08:02:45 -0600 Subject: [PATCH 22/42] handle missing exception types --- .../exception/GlobalExceptionHandler.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index 3801b3ea..092263d9 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -11,10 +11,13 @@ import org.springframework.security.authentication.DisabledException; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.resource.NoResourceFoundException; @@ -117,11 +120,31 @@ public class GlobalExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName(), ex, request); } + @ExceptionHandler(org.hibernate.query.PathException.class) + public ResponseEntity handleHibernatePathException(org.hibernate.query.PathException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid query field reference", ex, request); + } + @ExceptionHandler(PetService.ForbiddenImageAccessException.class) public ResponseEntity handleForbiddenImageAccess(PetService.ForbiddenImageAccessException ex, HttpServletRequest request) { return buildErrorResponse(HttpStatus.FORBIDDEN, "Access to this pet image is not allowed", ex, request); } + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid or missing request body", ex, request); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingParam(MissingServletRequestParameterException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Missing required parameter: " + ex.getParameterName(), ex, request); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + public ResponseEntity handleMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Unsupported content type", ex, request); + } + @ExceptionHandler(IOException.class) public ResponseEntity handleIOException(IOException ex, HttpServletRequest request) { return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); -- 2.49.1 From bebdf7094ec1dc43ff361ca8de0301ccc818d79d Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 08:15:59 -0600 Subject: [PATCH 23/42] catch sort query exceptions --- .../backend/exception/GlobalExceptionHandler.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index 092263d9..ed070aed 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -120,8 +120,8 @@ public class GlobalExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName(), ex, request); } - @ExceptionHandler(org.hibernate.query.PathException.class) - public ResponseEntity handleHibernatePathException(org.hibernate.query.PathException ex, HttpServletRequest request) { + @ExceptionHandler({org.hibernate.query.PathException.class, org.hibernate.query.sqm.PathElementException.class}) + public ResponseEntity handleHibernatePathException(Exception ex, HttpServletRequest request) { return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid query field reference", ex, request); } @@ -150,6 +150,11 @@ public class GlobalExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); } + @ExceptionHandler(org.springframework.dao.InvalidDataAccessApiUsageException.class) + public ResponseEntity handleInvalidDataAccess(org.springframework.dao.InvalidDataAccessApiUsageException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid query or sort parameter", ex, request); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex, HttpServletRequest request) { String message = ex.getMessage() == null || ex.getMessage().isBlank() -- 2.49.1 From 72a3e8d128303499f80a705f037c692c6ec0cb05 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 08:24:28 -0600 Subject: [PATCH 24/42] auto-complete scheduled appointments --- .../petshop/backend/repository/AppointmentRepository.java | 2 +- .../com/petshop/backend/service/AppointmentService.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index b23b1902..774c1427 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -48,7 +48,7 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.id IN :employeeIds AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") List findByEmployeeIdInAndAppointmentDate(@Param("employeeIds") List employeeIds, @Param("date") LocalDate date); - @Query("SELECT a FROM Appointment a WHERE (a.appointmentDate < :currentDate OR (a.appointmentDate = :currentDate AND a.appointmentTime < :currentTime)) AND LOWER(a.appointmentStatus) = 'booked'") + @Query("SELECT a FROM Appointment a WHERE (a.appointmentDate < :currentDate OR (a.appointmentDate = :currentDate AND a.appointmentTime < :currentTime)) AND LOWER(a.appointmentStatus) IN ('booked', 'scheduled')") List findPastBookedAppointments(@Param("currentDate") LocalDate currentDate, @Param("currentTime") LocalTime currentTime); List findByPet_Id(Long petId); diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index 832dc12e..048bdc37 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -279,8 +279,12 @@ public class AppointmentService { LocalDate tomorrow = currentDate.plusDays(1); - List tomorrowAppointments = appointmentRepository + List tomorrowBooked = appointmentRepository .findByAppointmentDateAndAppointmentStatusIgnoreCase(tomorrow, "Booked"); + List tomorrowScheduled = appointmentRepository + .findByAppointmentDateAndAppointmentStatusIgnoreCase(tomorrow, "Scheduled"); + List tomorrowAppointments = new java.util.ArrayList<>(tomorrowBooked); + tomorrowAppointments.addAll(tomorrowScheduled); for (Appointment appointment : tomorrowAppointments) { eventPublisher.publishEvent(new AppointmentReminderEvent(appointment.getAppointmentId())); } -- 2.49.1 From 6171b0f2f58ab714094f0f9227a14a3ae3fd9c2c Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 09:42:21 -0600 Subject: [PATCH 25/42] title-case all DB strings --- .../backend/controller/AuthController.java | 4 +- .../dto/adoption/CustomerAdoptionRequest.java | 2 +- .../dto/auth/ProfileUpdateRequest.java | 3 + .../backend/dto/chat/MessageRequest.java | 1 + .../backend/dto/common/CouponRequest.java | 1 + .../petshop/backend/dto/sale/SaleRequest.java | 5 + .../java/com/petshop/backend/entity/Sale.java | 2 +- .../backend/service/CouponService.java | 2 +- .../petshop/backend/service/SaleService.java | 2 +- .../db/migration/V1__target_baseline.sql | 2 +- .../resources/db/migration/V2__seed_data.sql | 578 +++++++++--------- 11 files changed, 307 insertions(+), 295 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index eb91c6dc..032518ec 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -344,9 +344,11 @@ public class AuthController { @PostMapping("/logout") public ResponseEntity logout() { + User user = authHelper.getAuthenticatedUser(); + user.setTokenVersion(user.getTokenVersion() + 1); + userRepository.save(user); Map response = new HashMap<>(); response.put("message", "Logged out successfully"); - response.put("note", "Token remains valid until expiration. Clear token from client storage."); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java b/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java index 287c1d6d..4272b2db 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java @@ -12,7 +12,7 @@ public class CustomerAdoptionRequest { private Long sourceStoreId; - @NotNull(message = "Appointment date is required") + @NotNull(message = "Adoption date is required") private LocalDate adoptionDate; public Long getPetId() { diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java index dc1c98c0..97b978b0 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java @@ -1,14 +1,17 @@ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import java.util.Objects; public class ProfileUpdateRequest { @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + @Pattern(regexp = "^(?!\\s*$).+", message = "Username must not be blank") private String username; @Email(message = "Email must be valid") + @Pattern(regexp = "^(?!\\s*$).+", message = "Email must not be blank") private String email; @Size(max = 50, message = "First name must not exceed 50 characters") diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java index cecedfbd..37dcd683 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java @@ -1,6 +1,7 @@ package com.petshop.backend.dto.chat; public class MessageRequest { + @jakarta.validation.constraints.Size(max = 10000, message = "Message content must not exceed 10000 characters") private String content; private String attachmentUrl; private String attachmentName; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java b/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java index 83531a74..ffa7f766 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java @@ -11,6 +11,7 @@ public class CouponRequest { private String couponCode; @NotBlank(message = "Discount type is required") + @jakarta.validation.constraints.Pattern(regexp = "^(Percent|Percentage|Fixed|Flat)$", flags = jakarta.validation.constraints.Pattern.Flag.CASE_INSENSITIVE, message = "Discount type must be Percent, Percentage, Fixed, or Flat") private String discountType; @NotNull(message = "Discount value is required") diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index db9eb6cd..b0e7989f 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -1,8 +1,10 @@ package com.petshop.backend.dto.sale; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.util.List; import java.util.Objects; @@ -10,6 +12,8 @@ public class SaleRequest { @NotNull(message = "Store ID is required") private Long storeId; + @NotBlank(message = "Payment method is required") + @Pattern(regexp = "^(Cash|Card)$", message = "Payment method must be Cash or Card") private String paymentMethod; @NotEmpty(message = "At least one item is required") @@ -22,6 +26,7 @@ public class SaleRequest { private Long customerId; + @Pattern(regexp = "^(In Store|Online)$", message = "Channel must be In Store or Online") private String channel; private Long couponId; diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java index b052e17d..d1134b58 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -47,7 +47,7 @@ public class Sale { private Sale originalSale; @Column(nullable = false, length = 20) - private String channel = "IN_STORE"; + private String channel = "In Store"; @ManyToOne @JoinColumn(name = "cartId") diff --git a/backend/src/main/java/com/petshop/backend/service/CouponService.java b/backend/src/main/java/com/petshop/backend/service/CouponService.java index 18ff269c..cf56188f 100644 --- a/backend/src/main/java/com/petshop/backend/service/CouponService.java +++ b/backend/src/main/java/com/petshop/backend/service/CouponService.java @@ -89,7 +89,7 @@ public class CouponService { } private void updateCouponFields(Coupon coupon, CouponRequest request) { - if ("PERCENTAGE".equalsIgnoreCase(request.getDiscountType()) + if (isPercentageType(request.getDiscountType()) && request.getDiscountValue().compareTo(new BigDecimal("100")) >= 0) { throw new BusinessException("Percentage discount must be less than 100"); } diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index c35c07da..d33d11b3 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -84,7 +84,7 @@ public class SaleService { sale.setStore(store); sale.setPaymentMethod(normalizePaymentMethod(request.getPaymentMethod())); sale.setIsRefund(request.getIsRefund() != null ? request.getIsRefund() : false); - sale.setChannel(request.getChannel() != null ? request.getChannel() : "IN_STORE"); + sale.setChannel(request.getChannel() != null ? request.getChannel() : "In Store"); if (request.getCouponId() != null) { Coupon coupon = couponRepository.findByIdForUpdate(request.getCouponId()) diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql index 01a0c34e..bb5d92f3 100644 --- a/backend/src/main/resources/db/migration/V1__target_baseline.sql +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -244,7 +244,7 @@ CREATE TABLE IF NOT EXISTS sale ( customerId BIGINT NULL, isRefund BOOLEAN NOT NULL DEFAULT FALSE, originalSaleId BIGINT NULL, - channel VARCHAR(20) NOT NULL DEFAULT 'IN_STORE', + channel VARCHAR(20) NOT NULL DEFAULT 'In Store', cartId BIGINT NULL, couponId BIGINT NULL, subtotalAmount DECIMAL(10, 2) NULL, diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 4109ec6a..0a307195 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -59,19 +59,19 @@ INSERT INTO storeLocation (storeId, storeName, address, phone, email, imageUrl) INSERT INTO users (id, username, password, email, firstName, lastName, fullName, phone, avatarUrl, role, staffRole, primaryStoreId, loyaltyPoints, active, tokenVersion) VALUES (1, 'admin', '$2y$10$ok/BmOn/pyyamTeNmUDiB.OfLCduQlZSAaRLlupM/cZb7ZhiBriVe', 'admin@petshop.com', 'Admin', 'User', 'Admin User', '000-000-1000', 'https://images.petshop.local/users/001.webp', 'ADMIN', 'ADMINISTRATOR', 1, 0, 1, 0), -(2, 'morgan.lee', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'morgan.lee@petshop.com', 'Morgan', 'Lee', 'Morgan Lee', '403-700-0002', 'https://images.petshop.local/users/002.webp', 'ADMIN', 'OPERATIONS_ADMIN', 2, 0, 1, 0), -(3, 'staff', '$2y$10$23mqbLolo609T/.PC4KfiuY.9HqYEgA8LrJ/fccZ7CmK0/OIsPrfq', 'staff@petshop.com', 'Staff', 'User', 'Staff User', '000-000-1001', 'https://images.petshop.local/users/003.webp', 'STAFF', 'STORE_MANAGER', 1, 0, 1, 0), -(4, 'sara.smith', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sara.smith@petshop.com', 'Sara', 'Smith', 'Sara Smith', '403-710-0004', 'https://images.petshop.local/users/004.webp', 'STAFF', 'SALES_ASSOCIATE', 1, 0, 1, 0), -(5, 'david.brown', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'david.brown@petshop.com', 'David', 'Brown', 'David Brown', '403-710-0005', 'https://images.petshop.local/users/005.webp', 'STAFF', 'VETERINARY_TECH', 1, 0, 1, 0), -(6, 'priya.patel', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'priya.patel@petshop.com', 'Priya', 'Patel', 'Priya Patel', '403-710-0006', 'https://images.petshop.local/users/006.webp', 'STAFF', 'GROOMER', 1, 0, 1, 0), -(7, 'michael.johnson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'michael.johnson@petshop.com', 'Michael', 'Johnson', 'Michael Johnson', '403-710-0007', 'https://images.petshop.local/users/007.webp', 'STAFF', 'STORE_MANAGER', 2, 0, 1, 0), -(8, 'emma.davis', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'emma.davis@petshop.com', 'Emma', 'Davis', 'Emma Davis', '403-710-0008', 'https://images.petshop.local/users/008.webp', 'STAFF', 'SALES_ASSOCIATE', 2, 0, 1, 0), -(9, 'lucas.turner', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lucas.turner@petshop.com', 'Lucas', 'Turner', 'Lucas Turner', '403-710-0009', 'https://images.petshop.local/users/009.webp', 'STAFF', 'VETERINARY_TECH', 2, 0, 1, 0), -(10, 'nina.green', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'nina.green@petshop.com', 'Nina', 'Green', 'Nina Green', '403-710-0010', 'https://images.petshop.local/users/010.webp', 'STAFF', 'GROOMER', 2, 0, 1, 0), -(11, 'lisa.williams', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lisa.williams@petshop.com', 'Lisa', 'Williams', 'Lisa Williams', '403-710-0011', 'https://images.petshop.local/users/011.webp', 'STAFF', 'STORE_MANAGER', 3, 0, 1, 0), -(12, 'daniel.moore', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'daniel.moore@petshop.com', 'Daniel', 'Moore', 'Daniel Moore', '403-710-0012', 'https://images.petshop.local/users/012.webp', 'STAFF', 'SALES_ASSOCIATE', 3, 0, 1, 0), -(13, 'chloe.martin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'chloe.martin@petshop.com', 'Chloe', 'Martin', 'Chloe Martin', '403-710-0013', 'https://images.petshop.local/users/013.webp', 'STAFF', 'VETERINARY_TECH', 3, 0, 1, 0), -(14, 'owen.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'owen.baker@petshop.com', 'Owen', 'Baker', 'Owen Baker', '403-710-0014', 'https://images.petshop.local/users/014.webp', 'STAFF', 'GROOMER', 3, 0, 1, 0), +(2, 'morgan.lee', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'morgan.lee@petshop.com', 'Morgan', 'Lee', 'Morgan Lee', '403-700-0002', 'https://images.petshop.local/users/002.webp', 'ADMIN', 'Operations Admin', 2, 0, 1, 0), +(3, 'staff', '$2y$10$23mqbLolo609T/.PC4KfiuY.9HqYEgA8LrJ/fccZ7CmK0/OIsPrfq', 'staff@petshop.com', 'Staff', 'User', 'Staff User', '000-000-1001', 'https://images.petshop.local/users/003.webp', 'STAFF', 'Store Manager', 1, 0, 1, 0), +(4, 'sara.smith', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sara.smith@petshop.com', 'Sara', 'Smith', 'Sara Smith', '403-710-0004', 'https://images.petshop.local/users/004.webp', 'STAFF', 'Sales Associate', 1, 0, 1, 0), +(5, 'david.brown', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'david.brown@petshop.com', 'David', 'Brown', 'David Brown', '403-710-0005', 'https://images.petshop.local/users/005.webp', 'STAFF', 'Veterinary Tech', 1, 0, 1, 0), +(6, 'priya.patel', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'priya.patel@petshop.com', 'Priya', 'Patel', 'Priya Patel', '403-710-0006', 'https://images.petshop.local/users/006.webp', 'STAFF', 'Groomer', 1, 0, 1, 0), +(7, 'michael.johnson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'michael.johnson@petshop.com', 'Michael', 'Johnson', 'Michael Johnson', '403-710-0007', 'https://images.petshop.local/users/007.webp', 'STAFF', 'Store Manager', 2, 0, 1, 0), +(8, 'emma.davis', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'emma.davis@petshop.com', 'Emma', 'Davis', 'Emma Davis', '403-710-0008', 'https://images.petshop.local/users/008.webp', 'STAFF', 'Sales Associate', 2, 0, 1, 0), +(9, 'lucas.turner', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lucas.turner@petshop.com', 'Lucas', 'Turner', 'Lucas Turner', '403-710-0009', 'https://images.petshop.local/users/009.webp', 'STAFF', 'Veterinary Tech', 2, 0, 1, 0), +(10, 'nina.green', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'nina.green@petshop.com', 'Nina', 'Green', 'Nina Green', '403-710-0010', 'https://images.petshop.local/users/010.webp', 'STAFF', 'Groomer', 2, 0, 1, 0), +(11, 'lisa.williams', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lisa.williams@petshop.com', 'Lisa', 'Williams', 'Lisa Williams', '403-710-0011', 'https://images.petshop.local/users/011.webp', 'STAFF', 'Store Manager', 3, 0, 1, 0), +(12, 'daniel.moore', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'daniel.moore@petshop.com', 'Daniel', 'Moore', 'Daniel Moore', '403-710-0012', 'https://images.petshop.local/users/012.webp', 'STAFF', 'Sales Associate', 3, 0, 1, 0), +(13, 'chloe.martin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'chloe.martin@petshop.com', 'Chloe', 'Martin', 'Chloe Martin', '403-710-0013', 'https://images.petshop.local/users/013.webp', 'STAFF', 'Veterinary Tech', 3, 0, 1, 0), +(14, 'owen.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'owen.baker@petshop.com', 'Owen', 'Baker', 'Owen Baker', '403-710-0014', 'https://images.petshop.local/users/014.webp', 'STAFF', 'Groomer', 3, 0, 1, 0), (15, 'customer', '$2y$10$fgIlTHDYUOzvbczwdhQP7..YuAHr2cGODb9OBQJqole3AkiY4CGUq', 'customer@petshop.com', 'Test', 'Customer', 'Test Customer', '000-000-1002', 'https://images.petshop.local/users/015.webp', 'CUSTOMER', 'CUSTOMER', NULL, 0, 1, 0), (16, 'alex.brown', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.brown@gmail.com', 'Alex', 'Brown', 'Alex Brown', '403-730-0016', 'https://images.petshop.local/users/016.webp', 'CUSTOMER', 'CUSTOMER', NULL, 12, 1, 0), (17, 'alex.clark', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.clark@gmail.com', 'Alex', 'Clark', 'Alex Clark', '403-730-0017', 'https://images.petshop.local/users/017.webp', 'CUSTOMER', 'CUSTOMER', NULL, 15, 1, 0), @@ -830,14 +830,14 @@ INSERT INTO purchaseOrder (purchaseOrderId, supId, storeId, orderDate) VALUES (36, 10, 3, '2026-04-22'); INSERT INTO coupon (couponId, couponCode, discountType, discountValue, minOrderAmount, active, startsAt, endsAt, usageLimit) VALUES -(1, 'NOCODE', 'FIXED', 0.00, 0.00, 1, NULL, NULL, NULL), -(2, 'WELCOME10', 'PERCENT', 10.00, 50.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 300), -(3, 'TREAT5', 'FIXED', 5.00, 25.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 500), -(4, 'GROOM15', 'PERCENT', 15.00, 60.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 200), -(5, 'FISHCARE8', 'FIXED', 8.00, 40.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 150), -(6, 'BIRD10', 'PERCENT', 10.00, 30.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 150), -(7, 'SPRING12', 'PERCENT', 12.00, 75.00, 1, '2026-03-01 00:00:00', '2026-05-31 23:59:59', 180), -(8, 'NEWPET20', 'FIXED', 20.00, 100.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 120); +(1, 'NOCODE', 'Fixed', 0.00, 0.00, 1, NULL, NULL, NULL), +(2, 'WELCOME10', 'Percent', 10.00, 50.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 300), +(3, 'TREAT5', 'Fixed', 5.00, 25.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 500), +(4, 'GROOM15', 'Percent', 15.00, 60.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 200), +(5, 'FISHCARE8', 'Fixed', 8.00, 40.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 150), +(6, 'BIRD10', 'Percent', 10.00, 30.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 150), +(7, 'SPRING12', 'Percent', 12.00, 75.00, 1, '2026-03-01 00:00:00', '2026-05-31 23:59:59', 180), +(8, 'NEWPET20', 'Fixed', 20.00, 100.00, 1, '2026-01-01 00:00:00', '2026-12-31 23:59:59', 120); INSERT INTO pet (petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, imageUrl, ownerUserId, storeId) VALUES (1, 'Buddy', 'Dog', 'Corgi', 2, 'Available', 466.80, 'https://images.petshop.local/pets/001.webp', NULL, 1), @@ -942,96 +942,96 @@ INSERT INTO pet (petId, petName, petSpecies, petBreed, petAge, petStatus, petPri (100, 'Comet', 'Bird', 'Lovebird', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/100.webp', 79, NULL); INSERT INTO appointment (appointmentId, serviceId, petId, customerId, storeId, employeeId, appointmentDate, appointmentTime, appointmentStatus) VALUES -(1, 2, 37, 16, 1, 3, '2026-01-07', '09:00:00', 'COMPLETED'), -(2, 8, 38, 17, 2, 8, '2026-01-09', '10:30:00', 'COMPLETED'), -(3, 4, 39, 18, 3, 13, '2026-01-11', '13:00:00', 'MISSED'), -(4, 8, 40, 19, 1, 6, '2026-01-13', '14:30:00', 'CANCELLED'), -(5, 5, 41, 20, 2, 7, '2026-01-15', '16:00:00', 'COMPLETED'), -(6, 8, 42, 21, 3, 12, '2026-01-17', '09:00:00', 'COMPLETED'), -(7, 2, 43, 22, 1, 5, '2026-01-19', '10:30:00', 'COMPLETED'), -(8, 8, 44, 23, 2, 10, '2026-01-21', '13:00:00', 'MISSED'), -(9, 4, 45, 24, 3, 11, '2026-01-23', '14:30:00', 'CANCELLED'), -(10, 8, 46, 25, 1, 4, '2026-01-25', '16:00:00', 'COMPLETED'), -(11, 6, 47, 26, 2, 9, '2026-01-27', '09:00:00', 'COMPLETED'), -(12, 7, 48, 27, 3, 14, '2026-01-29', '10:30:00', 'COMPLETED'), -(13, 2, 49, 28, 1, 3, '2026-01-31', '13:00:00', 'MISSED'), -(14, 4, 50, 29, 2, 8, '2026-02-02', '14:30:00', 'CANCELLED'), -(15, 5, 51, 30, 3, 13, '2026-02-04', '16:00:00', 'COMPLETED'), -(16, 1, 52, 31, 1, 6, '2026-02-06', '09:00:00', 'COMPLETED'), -(17, 2, 53, 32, 2, 7, '2026-02-08', '10:30:00', 'COMPLETED'), -(18, 3, 54, 33, 3, 12, '2026-02-10', '13:00:00', 'MISSED'), -(19, 4, 55, 34, 2, 9, '2026-02-12', '14:30:00', 'CANCELLED'), -(20, 5, 56, 35, 3, 14, '2026-02-14', '16:00:00', 'COMPLETED'), -(21, 1, 57, 36, 1, 3, '2026-02-16', '09:00:00', 'COMPLETED'), -(22, 4, 58, 37, 2, 8, '2026-02-18', '10:30:00', 'COMPLETED'), -(23, 4, 59, 38, 3, 13, '2026-02-20', '13:00:00', 'MISSED'), -(24, 5, 60, 39, 1, 6, '2026-02-22', '14:30:00', 'CANCELLED'), -(25, 2, 61, 40, 2, 7, '2026-02-24', '16:00:00', 'COMPLETED'), -(26, 1, 62, 41, 3, 12, '2026-02-26', '09:00:00', 'COMPLETED'), -(27, 2, 63, 42, 1, 5, '2026-02-28', '10:30:00', 'COMPLETED'), -(28, 3, 64, 43, 2, 10, '2026-03-02', '13:00:00', 'MISSED'), -(29, 2, 65, 44, 3, 11, '2026-03-04', '14:30:00', 'CANCELLED'), -(30, 8, 66, 45, 1, 4, '2026-03-06', '16:00:00', 'COMPLETED'), -(31, 2, 67, 46, 2, 9, '2026-03-08', '09:00:00', 'COMPLETED'), -(32, 5, 68, 47, 3, 14, '2026-03-10', '10:30:00', 'COMPLETED'), -(33, 3, 69, 48, 1, 3, '2026-03-12', '13:00:00', 'MISSED'), -(34, 4, 70, 49, 2, 8, '2026-03-14', '14:30:00', 'CANCELLED'), -(35, 5, 71, 50, 3, 13, '2026-03-16', '16:00:00', 'COMPLETED'), -(36, 7, 72, 51, 1, 6, '2026-03-18', '09:00:00', 'COMPLETED'), -(37, 4, 73, 52, 2, 7, '2026-03-20', '10:30:00', 'COMPLETED'), -(38, 4, 74, 53, 3, 12, '2026-03-22', '13:00:00', 'MISSED'), -(39, 4, 75, 54, 1, 5, '2026-03-24', '14:30:00', 'CANCELLED'), -(40, 5, 76, 55, 2, 10, '2026-03-26', '16:00:00', 'COMPLETED'), -(41, 1, 77, 56, 3, 11, '2026-03-28', '09:00:00', 'COMPLETED'), -(42, 2, 78, 57, 1, 4, '2026-03-30', '10:30:00', 'COMPLETED'), -(43, 6, 79, 58, 2, 9, '2026-04-01', '13:00:00', 'BOOKED'), -(44, 8, 80, 59, 3, 14, '2026-04-03', '14:30:00', 'BOOKED'), -(45, 5, 81, 60, 1, 3, '2026-04-05', '16:00:00', 'BOOKED'), -(46, 3, 82, 61, 2, 8, '2026-04-07', '09:00:00', 'BOOKED'), -(47, 2, 83, 62, 3, 13, '2026-04-09', '10:30:00', 'BOOKED'), -(48, 3, 84, 63, 1, 6, '2026-04-11', '13:00:00', 'BOOKED'), -(49, 4, 85, 64, 2, 7, '2026-04-13', '14:30:00', 'BOOKED'), -(50, 4, 86, 65, 3, 12, '2026-04-15', '16:00:00', 'BOOKED'), -(51, 4, 87, 66, 1, 5, '2026-04-17', '09:00:00', 'BOOKED'), -(52, 2, 88, 67, 2, 10, '2026-04-19', '10:30:00', 'BOOKED'), -(53, 2, 89, 68, 3, 11, '2026-04-21', '13:00:00', 'BOOKED'), -(54, 4, 90, 69, 1, 4, '2026-04-23', '14:30:00', 'BOOKED'), -(55, 5, 91, 70, 2, 9, '2026-04-25', '16:00:00', 'BOOKED'), -(56, 1, 92, 71, 3, 14, '2026-04-27', '09:00:00', 'BOOKED'), -(57, 2, 93, 72, 1, 3, '2026-04-29', '10:30:00', 'BOOKED'), -(58, 8, 94, 73, 2, 8, '2026-05-01', '13:00:00', 'BOOKED'), -(59, 4, 95, 74, 3, 13, '2026-05-03', '14:30:00', 'BOOKED'), -(60, 5, 96, 75, 1, 6, '2026-05-05', '16:00:00', 'BOOKED'), -(61, 1, 97, 76, 2, 7, '2026-01-07', '09:00:00', 'COMPLETED'), -(62, 2, 98, 77, 3, 12, '2026-01-09', '10:30:00', 'COMPLETED'), -(63, 3, 99, 78, 1, 5, '2026-01-11', '13:00:00', 'MISSED'), -(64, 7, 100, 79, 2, 10, '2026-01-13', '14:30:00', 'CANCELLED'), -(65, 2, 37, 16, 1, 3, '2026-01-15', '16:00:00', 'COMPLETED'), -(66, 8, 38, 17, 2, 8, '2026-01-17', '09:00:00', 'COMPLETED'), -(67, 4, 39, 18, 3, 13, '2026-01-19', '10:30:00', 'COMPLETED'), -(68, 8, 40, 19, 1, 6, '2026-01-21', '13:00:00', 'MISSED'), -(69, 4, 41, 20, 2, 7, '2026-01-23', '14:30:00', 'CANCELLED'), -(70, 8, 42, 21, 3, 12, '2026-01-25', '16:00:00', 'COMPLETED'), -(71, 1, 43, 22, 1, 5, '2026-01-27', '09:00:00', 'COMPLETED'), -(72, 8, 44, 23, 2, 10, '2026-01-29', '10:30:00', 'COMPLETED'), -(73, 4, 45, 24, 3, 11, '2026-01-31', '13:00:00', 'MISSED'), -(74, 8, 46, 25, 1, 4, '2026-02-02', '14:30:00', 'CANCELLED'), -(75, 6, 47, 26, 2, 9, '2026-02-04', '16:00:00', 'COMPLETED'), -(76, 7, 48, 27, 3, 14, '2026-02-06', '09:00:00', 'COMPLETED'), -(77, 2, 49, 28, 1, 3, '2026-02-08', '10:30:00', 'COMPLETED'), -(78, 4, 50, 29, 2, 8, '2026-02-10', '13:00:00', 'MISSED'), -(79, 4, 51, 30, 3, 13, '2026-02-12', '14:30:00', 'CANCELLED'), -(80, 5, 52, 31, 1, 6, '2026-02-14', '16:00:00', 'COMPLETED'), -(81, 1, 53, 32, 2, 7, '2026-02-16', '09:00:00', 'COMPLETED'), -(82, 2, 54, 33, 3, 12, '2026-02-18', '10:30:00', 'COMPLETED'), -(83, 3, 55, 34, 2, 9, '2026-02-20', '13:00:00', 'MISSED'), -(84, 4, 56, 35, 3, 14, '2026-02-22', '14:30:00', 'CANCELLED'), -(85, 5, 57, 36, 1, 3, '2026-02-24', '16:00:00', 'COMPLETED'), -(86, 4, 58, 37, 2, 8, '2026-02-26', '09:00:00', 'COMPLETED'), -(87, 4, 59, 38, 3, 13, '2026-02-28', '10:30:00', 'COMPLETED'), -(88, 2, 60, 39, 1, 6, '2026-03-02', '13:00:00', 'MISSED'), -(89, 2, 61, 40, 2, 7, '2026-03-04', '14:30:00', 'CANCELLED'), -(90, 5, 62, 41, 3, 12, '2026-03-06', '16:00:00', 'COMPLETED'); +(1, 2, 37, 16, 1, 3, '2026-01-07', '09:00:00', 'Completed'), +(2, 8, 38, 17, 2, 8, '2026-01-09', '10:30:00', 'Completed'), +(3, 4, 39, 18, 3, 13, '2026-01-11', '13:00:00', 'Missed'), +(4, 8, 40, 19, 1, 6, '2026-01-13', '14:30:00', 'Cancelled'), +(5, 5, 41, 20, 2, 7, '2026-01-15', '16:00:00', 'Completed'), +(6, 8, 42, 21, 3, 12, '2026-01-17', '09:00:00', 'Completed'), +(7, 2, 43, 22, 1, 5, '2026-01-19', '10:30:00', 'Completed'), +(8, 8, 44, 23, 2, 10, '2026-01-21', '13:00:00', 'Missed'), +(9, 4, 45, 24, 3, 11, '2026-01-23', '14:30:00', 'Cancelled'), +(10, 8, 46, 25, 1, 4, '2026-01-25', '16:00:00', 'Completed'), +(11, 6, 47, 26, 2, 9, '2026-01-27', '09:00:00', 'Completed'), +(12, 7, 48, 27, 3, 14, '2026-01-29', '10:30:00', 'Completed'), +(13, 2, 49, 28, 1, 3, '2026-01-31', '13:00:00', 'Missed'), +(14, 4, 50, 29, 2, 8, '2026-02-02', '14:30:00', 'Cancelled'), +(15, 5, 51, 30, 3, 13, '2026-02-04', '16:00:00', 'Completed'), +(16, 1, 52, 31, 1, 6, '2026-02-06', '09:00:00', 'Completed'), +(17, 2, 53, 32, 2, 7, '2026-02-08', '10:30:00', 'Completed'), +(18, 3, 54, 33, 3, 12, '2026-02-10', '13:00:00', 'Missed'), +(19, 4, 55, 34, 2, 9, '2026-02-12', '14:30:00', 'Cancelled'), +(20, 5, 56, 35, 3, 14, '2026-02-14', '16:00:00', 'Completed'), +(21, 1, 57, 36, 1, 3, '2026-02-16', '09:00:00', 'Completed'), +(22, 4, 58, 37, 2, 8, '2026-02-18', '10:30:00', 'Completed'), +(23, 4, 59, 38, 3, 13, '2026-02-20', '13:00:00', 'Missed'), +(24, 5, 60, 39, 1, 6, '2026-02-22', '14:30:00', 'Cancelled'), +(25, 2, 61, 40, 2, 7, '2026-02-24', '16:00:00', 'Completed'), +(26, 1, 62, 41, 3, 12, '2026-02-26', '09:00:00', 'Completed'), +(27, 2, 63, 42, 1, 5, '2026-02-28', '10:30:00', 'Completed'), +(28, 3, 64, 43, 2, 10, '2026-03-02', '13:00:00', 'Missed'), +(29, 2, 65, 44, 3, 11, '2026-03-04', '14:30:00', 'Cancelled'), +(30, 8, 66, 45, 1, 4, '2026-03-06', '16:00:00', 'Completed'), +(31, 2, 67, 46, 2, 9, '2026-03-08', '09:00:00', 'Completed'), +(32, 5, 68, 47, 3, 14, '2026-03-10', '10:30:00', 'Completed'), +(33, 3, 69, 48, 1, 3, '2026-03-12', '13:00:00', 'Missed'), +(34, 4, 70, 49, 2, 8, '2026-03-14', '14:30:00', 'Cancelled'), +(35, 5, 71, 50, 3, 13, '2026-03-16', '16:00:00', 'Completed'), +(36, 7, 72, 51, 1, 6, '2026-03-18', '09:00:00', 'Completed'), +(37, 4, 73, 52, 2, 7, '2026-03-20', '10:30:00', 'Completed'), +(38, 4, 74, 53, 3, 12, '2026-03-22', '13:00:00', 'Missed'), +(39, 4, 75, 54, 1, 5, '2026-03-24', '14:30:00', 'Cancelled'), +(40, 5, 76, 55, 2, 10, '2026-03-26', '16:00:00', 'Completed'), +(41, 1, 77, 56, 3, 11, '2026-03-28', '09:00:00', 'Completed'), +(42, 2, 78, 57, 1, 4, '2026-03-30', '10:30:00', 'Completed'), +(43, 6, 79, 58, 2, 9, '2026-04-01', '13:00:00', 'Booked'), +(44, 8, 80, 59, 3, 14, '2026-04-03', '14:30:00', 'Booked'), +(45, 5, 81, 60, 1, 3, '2026-04-05', '16:00:00', 'Booked'), +(46, 3, 82, 61, 2, 8, '2026-04-07', '09:00:00', 'Booked'), +(47, 2, 83, 62, 3, 13, '2026-04-09', '10:30:00', 'Booked'), +(48, 3, 84, 63, 1, 6, '2026-04-11', '13:00:00', 'Booked'), +(49, 4, 85, 64, 2, 7, '2026-04-13', '14:30:00', 'Booked'), +(50, 4, 86, 65, 3, 12, '2026-04-15', '16:00:00', 'Booked'), +(51, 4, 87, 66, 1, 5, '2026-04-17', '09:00:00', 'Booked'), +(52, 2, 88, 67, 2, 10, '2026-04-19', '10:30:00', 'Booked'), +(53, 2, 89, 68, 3, 11, '2026-04-21', '13:00:00', 'Booked'), +(54, 4, 90, 69, 1, 4, '2026-04-23', '14:30:00', 'Booked'), +(55, 5, 91, 70, 2, 9, '2026-04-25', '16:00:00', 'Booked'), +(56, 1, 92, 71, 3, 14, '2026-04-27', '09:00:00', 'Booked'), +(57, 2, 93, 72, 1, 3, '2026-04-29', '10:30:00', 'Booked'), +(58, 8, 94, 73, 2, 8, '2026-05-01', '13:00:00', 'Booked'), +(59, 4, 95, 74, 3, 13, '2026-05-03', '14:30:00', 'Booked'), +(60, 5, 96, 75, 1, 6, '2026-05-05', '16:00:00', 'Booked'), +(61, 1, 97, 76, 2, 7, '2026-01-07', '09:00:00', 'Completed'), +(62, 2, 98, 77, 3, 12, '2026-01-09', '10:30:00', 'Completed'), +(63, 3, 99, 78, 1, 5, '2026-01-11', '13:00:00', 'Missed'), +(64, 7, 100, 79, 2, 10, '2026-01-13', '14:30:00', 'Cancelled'), +(65, 2, 37, 16, 1, 3, '2026-01-15', '16:00:00', 'Completed'), +(66, 8, 38, 17, 2, 8, '2026-01-17', '09:00:00', 'Completed'), +(67, 4, 39, 18, 3, 13, '2026-01-19', '10:30:00', 'Completed'), +(68, 8, 40, 19, 1, 6, '2026-01-21', '13:00:00', 'Missed'), +(69, 4, 41, 20, 2, 7, '2026-01-23', '14:30:00', 'Cancelled'), +(70, 8, 42, 21, 3, 12, '2026-01-25', '16:00:00', 'Completed'), +(71, 1, 43, 22, 1, 5, '2026-01-27', '09:00:00', 'Completed'), +(72, 8, 44, 23, 2, 10, '2026-01-29', '10:30:00', 'Completed'), +(73, 4, 45, 24, 3, 11, '2026-01-31', '13:00:00', 'Missed'), +(74, 8, 46, 25, 1, 4, '2026-02-02', '14:30:00', 'Cancelled'), +(75, 6, 47, 26, 2, 9, '2026-02-04', '16:00:00', 'Completed'), +(76, 7, 48, 27, 3, 14, '2026-02-06', '09:00:00', 'Completed'), +(77, 2, 49, 28, 1, 3, '2026-02-08', '10:30:00', 'Completed'), +(78, 4, 50, 29, 2, 8, '2026-02-10', '13:00:00', 'Missed'), +(79, 4, 51, 30, 3, 13, '2026-02-12', '14:30:00', 'Cancelled'), +(80, 5, 52, 31, 1, 6, '2026-02-14', '16:00:00', 'Completed'), +(81, 1, 53, 32, 2, 7, '2026-02-16', '09:00:00', 'Completed'), +(82, 2, 54, 33, 3, 12, '2026-02-18', '10:30:00', 'Completed'), +(83, 3, 55, 34, 2, 9, '2026-02-20', '13:00:00', 'Missed'), +(84, 4, 56, 35, 3, 14, '2026-02-22', '14:30:00', 'Cancelled'), +(85, 5, 57, 36, 1, 3, '2026-02-24', '16:00:00', 'Completed'), +(86, 4, 58, 37, 2, 8, '2026-02-26', '09:00:00', 'Completed'), +(87, 4, 59, 38, 3, 13, '2026-02-28', '10:30:00', 'Completed'), +(88, 2, 60, 39, 1, 6, '2026-03-02', '13:00:00', 'Missed'), +(89, 2, 61, 40, 2, 7, '2026-03-04', '14:30:00', 'Cancelled'), +(90, 5, 62, 41, 3, 12, '2026-03-06', '16:00:00', 'Completed'); INSERT INTO adoption (adoptionId, petId, customerId, employeeId, sourceStoreId, adoptionDate, adoptionStatus) VALUES (1, 37, 16, 3, 1, '2026-01-08', 'Completed'), @@ -1217,116 +1217,116 @@ INSERT INTO cart_item (cartItemId, cartId, prodId, quantity, unitPrice) VALUES (119, 40, 85, 2, 46.43); INSERT INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES -(1, '2026-02-02 10:11:00', 37.83, 'Cash', 3, 1, 3, 0, NULL, 'ONLINE', 1, 1, 44.51, 0.00, 6.68, 0), -(2, '2026-02-03 10:22:00', 192.78, 'Card', 4, 1, 4, 0, NULL, 'ONLINE', 2, 2, 252.00, 25.20, 34.02, 0), -(3, '2026-02-04 10:33:00', 363.23, 'Card', 5, 1, 5, 0, NULL, 'ONLINE', 3, 3, 432.33, 5.00, 64.10, 0), -(4, '2026-02-05 10:44:00', 81.29, 'Cash', 6, 1, 6, 0, NULL, 'ONLINE', 4, 7, 108.67, 13.04, 14.34, 0), -(5, '2026-02-06 10:55:00', 435.73, 'Card', 7, 2, 7, 0, NULL, 'ONLINE', 5, 1, 512.62, 0.00, 76.89, 0), -(6, '2026-02-07 11:06:00', 409.56, 'Card', 8, 2, 8, 0, NULL, 'ONLINE', 6, 1, 481.83, 0.00, 72.27, 0), -(7, '2026-02-08 11:17:00', 56.40, 'Cash', 9, 2, 9, 0, NULL, 'ONLINE', 7, 4, 78.06, 11.71, 9.95, 0), -(8, '2026-02-09 11:28:00', 174.20, 'Card', 10, 2, 10, 0, NULL, 'ONLINE', 8, 5, 212.94, 8.00, 30.74, 0), -(9, '2026-02-10 11:39:00', 619.91, 'Card', 11, 3, 11, 0, NULL, 'ONLINE', 9, 1, 729.31, 0.00, 109.40, 0), -(10, '2026-02-11 11:50:00', 169.73, 'Card', 12, 3, 12, 0, NULL, 'ONLINE', 10, 6, 221.87, 22.19, 29.95, 0), -(11, '2026-02-12 12:01:00', 137.86, 'Cash', 13, 3, 13, 0, NULL, 'ONLINE', 11, 1, 162.19, 0.00, 24.33, 0), -(12, '2026-02-13 12:12:00', 453.95, 'Card', 14, 3, 14, 0, NULL, 'ONLINE', 12, 2, 593.40, 59.34, 80.11, 0), -(13, '2026-01-05 09:15:00', 82.72, 'Card', 3, 1, 15, 0, NULL, 'IN_STORE', NULL, 1, 82.72, 0.00, 0.00, 0), -(14, '2026-01-05 09:52:00', 120.43, 'Card', 8, 2, 16, 0, NULL, 'IN_STORE', NULL, 2, 133.81, 13.38, 0.00, 12), -(15, '2026-01-06 10:29:00', 153.21, 'Cash', 13, 3, 17, 0, NULL, 'IN_STORE', NULL, 1, 153.21, 0.00, 0.00, 15), -(16, '2026-01-06 11:06:00', 20.27, 'Card', 6, 1, 18, 0, NULL, 'IN_STORE', NULL, 3, 25.27, 5.00, 0.00, 2), -(17, '2026-01-07 11:43:00', 58.96, 'Cash', 7, 2, 19, 0, NULL, 'IN_STORE', NULL, 1, 58.96, 0.00, 0.00, 5), -(18, '2026-01-07 12:20:00', 124.54, 'Card', 12, 3, 20, 0, NULL, 'IN_STORE', NULL, 7, 141.52, 16.98, 0.00, 12), -(19, '2026-01-08 12:57:00', 118.84, 'Card', 5, 1, 21, 0, NULL, 'IN_STORE', NULL, 1, 118.84, 0.00, 0.00, 11), -(20, '2026-01-08 13:34:00', 167.02, 'Cash', 10, 2, 22, 0, NULL, 'IN_STORE', NULL, 4, 196.50, 29.48, 0.00, 16), -(21, '2026-01-09 14:11:00', 367.69, 'Card', 11, 3, 23, 0, NULL, 'IN_STORE', NULL, 1, 367.69, 0.00, 0.00, 36), -(22, '2026-01-09 14:48:00', 57.62, 'Cash', 4, 1, 24, 0, NULL, 'IN_STORE', NULL, 1, 57.62, 0.00, 0.00, 5), -(23, '2026-01-10 15:25:00', 84.03, 'Card', 9, 2, 25, 0, NULL, 'IN_STORE', NULL, 6, 93.37, 9.34, 0.00, 8), -(24, '2026-01-10 16:02:00', 297.25, 'Card', 14, 3, 26, 0, NULL, 'IN_STORE', NULL, 1, 297.25, 0.00, 0.00, 29), -(25, '2026-01-11 16:39:00', 35.78, 'Cash', 3, 1, 27, 0, NULL, 'IN_STORE', NULL, 2, 35.78, 0.00, 0.00, 3), -(26, '2026-01-11 17:16:00', 136.99, 'Card', 8, 2, 28, 0, NULL, 'IN_STORE', NULL, 1, 136.99, 0.00, 0.00, 13), -(27, '2026-01-12 17:53:00', 300.52, 'Cash', 13, 3, 29, 0, NULL, 'IN_STORE', NULL, 3, 305.52, 5.00, 0.00, 30), -(28, '2026-01-12 18:30:00', 165.99, 'Card', 6, 1, 30, 0, NULL, 'IN_STORE', NULL, 1, 165.99, 0.00, 0.00, 16), -(29, '2026-01-13 19:07:00', 91.28, 'Card', 7, 2, 31, 0, NULL, 'IN_STORE', NULL, 7, 103.73, 12.45, 0.00, 9), -(30, '2026-01-13 19:44:00', 198.88, 'Cash', 12, 3, 32, 0, NULL, 'IN_STORE', NULL, 1, 198.88, 0.00, 0.00, 19), -(31, '2026-01-14 20:21:00', 25.28, 'Card', 5, 1, 33, 0, NULL, 'IN_STORE', NULL, 4, 25.28, 0.00, 0.00, 2), -(32, '2026-01-14 20:58:00', 58.51, 'Cash', 10, 2, 34, 0, NULL, 'IN_STORE', NULL, 1, 58.51, 0.00, 0.00, 5), -(33, '2026-01-15 21:35:00', 314.15, 'Card', 11, 3, 35, 0, NULL, 'IN_STORE', NULL, 1, 314.15, 0.00, 0.00, 31), -(34, '2026-01-15 22:12:00', 61.62, 'Card', 4, 1, 36, 0, NULL, 'IN_STORE', NULL, 6, 68.47, 6.85, 0.00, 6), -(35, '2026-01-16 22:49:00', 49.61, 'Cash', 9, 2, 37, 0, NULL, 'IN_STORE', NULL, 1, 49.61, 0.00, 0.00, 4), -(36, '2026-01-16 23:26:00', 196.32, 'Card', 14, 3, 38, 0, NULL, 'IN_STORE', NULL, 2, 218.13, 21.81, 0.00, 19), -(37, '2026-01-18 00:03:00', 47.92, 'Cash', 3, 1, 39, 0, NULL, 'IN_STORE', NULL, 1, 47.92, 0.00, 0.00, 4), -(38, '2026-01-18 00:40:00', 121.03, 'Card', 8, 2, 40, 0, NULL, 'IN_STORE', NULL, 3, 126.03, 5.00, 0.00, 12), -(39, '2026-01-19 01:17:00', 187.91, 'Card', 13, 3, 41, 0, NULL, 'IN_STORE', NULL, 1, 187.91, 0.00, 0.00, 18), -(40, '2026-01-19 01:54:00', 108.22, 'Cash', 6, 1, 42, 0, NULL, 'IN_STORE', NULL, 7, 122.98, 14.76, 0.00, 10), -(41, '2026-01-20 02:31:00', 67.71, 'Card', 7, 2, 43, 0, NULL, 'IN_STORE', NULL, 1, 67.71, 0.00, 0.00, 6), -(42, '2026-01-20 03:08:00', 114.93, 'Cash', 12, 3, 44, 0, NULL, 'IN_STORE', NULL, 4, 135.21, 20.28, 0.00, 11), -(43, '2026-01-21 03:45:00', 55.38, 'Card', 5, 1, 45, 0, NULL, 'IN_STORE', NULL, 1, 55.38, 0.00, 0.00, 5), -(44, '2026-01-21 04:22:00', 286.34, 'Card', 10, 2, 46, 0, NULL, 'IN_STORE', NULL, 1, 286.34, 0.00, 0.00, 28), -(45, '2026-01-22 04:59:00', 83.62, 'Cash', 11, 3, 47, 0, NULL, 'IN_STORE', NULL, 6, 92.91, 9.29, 0.00, 8), -(46, '2026-01-22 05:36:00', 29.89, 'Card', 4, 1, 48, 0, NULL, 'IN_STORE', NULL, 1, 29.89, 0.00, 0.00, 2), -(47, '2026-01-23 06:13:00', 161.48, 'Cash', 9, 2, 49, 0, NULL, 'IN_STORE', NULL, 2, 179.42, 17.94, 0.00, 16), -(48, '2026-01-23 06:50:00', 210.14, 'Card', 14, 3, 50, 0, NULL, 'IN_STORE', NULL, 1, 210.14, 0.00, 0.00, 21), -(49, '2026-01-24 07:27:00', 73.64, 'Card', 3, 1, 51, 0, NULL, 'IN_STORE', NULL, 3, 78.64, 5.00, 0.00, 7), -(50, '2026-01-24 08:04:00', 179.28, 'Cash', 8, 2, 52, 0, NULL, 'IN_STORE', NULL, 1, 179.28, 0.00, 0.00, 17), -(51, '2026-01-25 08:41:00', 101.67, 'Card', 13, 3, 53, 0, NULL, 'IN_STORE', NULL, 7, 115.53, 13.86, 0.00, 10), -(52, '2026-01-25 09:18:00', 21.31, 'Cash', 6, 1, 54, 0, NULL, 'IN_STORE', NULL, 1, 21.31, 0.00, 0.00, 2), -(53, '2026-01-26 09:55:00', 79.57, 'Card', 7, 2, 55, 0, NULL, 'IN_STORE', NULL, 4, 93.61, 14.04, 0.00, 7), -(54, '2026-01-26 10:32:00', 156.49, 'Card', 12, 3, 56, 0, NULL, 'IN_STORE', NULL, 1, 156.49, 0.00, 0.00, 15), -(55, '2026-01-27 11:09:00', 28.32, 'Cash', 5, 1, 57, 0, NULL, 'IN_STORE', NULL, 1, 28.32, 0.00, 0.00, 2), -(56, '2026-01-27 11:46:00', 175.47, 'Card', 10, 2, 58, 0, NULL, 'IN_STORE', NULL, 6, 194.97, 19.50, 0.00, 17), -(57, '2026-01-28 12:23:00', 150.60, 'Cash', 11, 3, 59, 0, NULL, 'IN_STORE', NULL, 1, 150.60, 0.00, 0.00, 15), -(58, '2026-01-28 13:00:00', 45.73, 'Card', 4, 1, 60, 0, NULL, 'IN_STORE', NULL, 2, 45.73, 0.00, 0.00, 4), -(59, '2026-01-29 13:37:00', 132.93, 'Card', 9, 2, 61, 0, NULL, 'IN_STORE', NULL, 1, 132.93, 0.00, 0.00, 13), -(60, '2026-01-29 14:14:00', 261.49, 'Cash', 14, 3, 62, 0, NULL, 'IN_STORE', NULL, 3, 266.49, 5.00, 0.00, 26), -(61, '2026-01-30 14:51:00', 57.02, 'Card', 3, 1, 63, 0, NULL, 'IN_STORE', NULL, 1, 57.02, 0.00, 0.00, 5), -(62, '2026-01-30 15:28:00', 90.64, 'Cash', 8, 2, 64, 0, NULL, 'IN_STORE', NULL, 7, 103.00, 12.36, 0.00, 9), -(63, '2026-01-31 16:05:00', 228.91, 'Card', 13, 3, 65, 0, NULL, 'IN_STORE', NULL, 1, 228.91, 0.00, 0.00, 22), -(64, '2026-01-31 16:42:00', 50.36, 'Card', 6, 1, 66, 0, NULL, 'IN_STORE', NULL, 4, 50.36, 0.00, 0.00, 5), -(65, '2026-02-01 17:19:00', 53.77, 'Cash', 7, 2, 67, 0, NULL, 'IN_STORE', NULL, 1, 53.77, 0.00, 0.00, 5), -(66, '2026-02-01 17:56:00', 172.92, 'Card', 12, 3, 68, 0, NULL, 'IN_STORE', NULL, 1, 172.92, 0.00, 0.00, 17), -(67, '2026-02-02 18:33:00', 154.78, 'Cash', 5, 1, 69, 0, NULL, 'IN_STORE', NULL, 6, 171.98, 17.20, 0.00, 15), -(68, '2026-02-02 19:10:00', 198.21, 'Card', 10, 2, 70, 0, NULL, 'IN_STORE', NULL, 1, 198.21, 0.00, 0.00, 19), -(69, '2026-02-03 19:47:00', 134.85, 'Card', 11, 3, 71, 0, NULL, 'IN_STORE', NULL, 2, 149.83, 14.98, 0.00, 13), -(70, '2026-02-03 20:24:00', 74.88, 'Cash', 4, 1, 72, 0, NULL, 'IN_STORE', NULL, 1, 74.88, 0.00, 0.00, 7), -(71, '2026-02-04 21:01:00', 27.77, 'Card', 9, 2, 73, 0, NULL, 'IN_STORE', NULL, 3, 32.77, 5.00, 0.00, 2), -(72, '2026-02-04 21:38:00', 105.22, 'Cash', 14, 3, 74, 0, NULL, 'IN_STORE', NULL, 1, 105.22, 0.00, 0.00, 10), -(73, '2026-02-05 22:15:00', 72.79, 'Card', 3, 1, 75, 0, NULL, 'IN_STORE', NULL, 7, 82.72, 9.93, 0.00, 7), -(74, '2026-02-05 22:52:00', 133.81, 'Card', 8, 2, 76, 0, NULL, 'IN_STORE', NULL, 1, 133.81, 0.00, 0.00, 13), -(75, '2026-02-06 23:29:00', 130.23, 'Cash', 13, 3, 77, 0, NULL, 'IN_STORE', NULL, 4, 153.21, 22.98, 0.00, 13), -(76, '2026-02-07 00:06:00', 25.27, 'Card', 6, 1, 78, 0, NULL, 'IN_STORE', NULL, 1, 25.27, 0.00, 0.00, 2), -(77, '2026-02-08 00:43:00', 58.96, 'Cash', 7, 2, 79, 0, NULL, 'IN_STORE', NULL, 1, 58.96, 0.00, 0.00, 5), -(78, '2026-02-08 01:20:00', 127.37, 'Card', 12, 3, 80, 0, NULL, 'IN_STORE', NULL, 6, 141.52, 14.15, 0.00, 12), -(79, '2026-02-09 01:57:00', 118.84, 'Card', 5, 1, 81, 0, NULL, 'IN_STORE', NULL, 1, 118.84, 0.00, 0.00, 11), -(80, '2026-02-09 02:34:00', 176.85, 'Cash', 10, 2, 82, 0, NULL, 'IN_STORE', NULL, 2, 196.50, 19.65, 0.00, 17), -(81, '2026-02-10 03:11:00', 367.69, 'Card', 11, 3, 83, 0, NULL, 'IN_STORE', NULL, 1, 367.69, 0.00, 0.00, 36), -(82, '2026-02-10 03:48:00', 52.62, 'Cash', 4, 1, 84, 0, NULL, 'IN_STORE', NULL, 3, 57.62, 5.00, 0.00, 5), -(83, '2026-02-11 04:25:00', 93.37, 'Card', 9, 2, 85, 0, NULL, 'IN_STORE', NULL, 1, 93.37, 0.00, 0.00, 9), -(84, '2026-02-11 05:02:00', 261.58, 'Card', 14, 3, 86, 0, NULL, 'IN_STORE', NULL, 7, 297.25, 35.67, 0.00, 26), -(85, '2026-02-12 05:39:00', 35.78, 'Cash', 3, 1, 87, 0, NULL, 'IN_STORE', NULL, 1, 35.78, 0.00, 0.00, 3), -(86, '2026-02-12 06:16:00', 116.44, 'Card', 8, 2, 88, 0, NULL, 'IN_STORE', NULL, 4, 136.99, 20.55, 0.00, 11), -(87, '2026-02-13 06:53:00', 305.52, 'Cash', 13, 3, 89, 0, NULL, 'IN_STORE', NULL, 1, 305.52, 0.00, 0.00, 30), -(88, '2026-02-13 07:30:00', 165.99, 'Card', 6, 1, 90, 0, NULL, 'IN_STORE', NULL, 1, 165.99, 0.00, 0.00, 16), -(89, '2026-02-14 08:07:00', 93.36, 'Card', 7, 2, 91, 0, NULL, 'IN_STORE', NULL, 6, 103.73, 10.37, 0.00, 9), -(90, '2026-02-14 08:44:00', 198.88, 'Cash', 12, 3, 92, 0, NULL, 'IN_STORE', NULL, 1, 198.88, 0.00, 0.00, 19), -(91, '2026-02-15 09:21:00', 25.28, 'Card', 5, 1, 93, 0, NULL, 'IN_STORE', NULL, 2, 25.28, 0.00, 0.00, 2), -(92, '2026-02-15 09:58:00', 58.51, 'Cash', 10, 2, 94, 0, NULL, 'IN_STORE', NULL, 1, 58.51, 0.00, 0.00, 5), -(93, '2026-02-16 10:35:00', 309.15, 'Card', 11, 3, 95, 0, NULL, 'IN_STORE', NULL, 3, 314.15, 5.00, 0.00, 30), -(94, '2026-02-16 11:12:00', 68.47, 'Card', 4, 1, 96, 0, NULL, 'IN_STORE', NULL, 1, 68.47, 0.00, 0.00, 6), -(95, '2026-02-17 11:49:00', 49.61, 'Cash', 9, 2, 97, 0, NULL, 'IN_STORE', NULL, 7, 49.61, 0.00, 0.00, 4), -(96, '2026-02-13 10:11:00', 34.80, 'Card', 3, 1, 3, 1, 1, 'IN_STORE', NULL, 1, 34.80, 0.00, 0.00, 0), -(97, '2026-02-15 10:22:00', 88.32, 'Card', 4, 1, 4, 1, 2, 'IN_STORE', NULL, 1, 88.32, 0.00, 0.00, 0), -(98, '2026-02-17 10:33:00', 49.05, 'Card', 5, 1, 5, 1, 3, 'IN_STORE', NULL, 1, 49.05, 0.00, 0.00, 0), -(99, '2026-02-19 10:44:00', 15.25, 'Card', 6, 1, 6, 1, 4, 'IN_STORE', NULL, 1, 15.25, 0.00, 0.00, 0), -(100, '2026-02-21 10:55:00', 181.42, 'Card', 7, 2, 7, 1, 5, 'IN_STORE', NULL, 1, 181.42, 0.00, 0.00, 0), -(101, '2026-02-23 11:06:00', 130.82, 'Card', 8, 2, 8, 1, 6, 'IN_STORE', NULL, 1, 130.82, 0.00, 0.00, 0), -(102, '2026-02-25 11:17:00', 50.37, 'Card', 9, 2, 9, 1, 7, 'IN_STORE', NULL, 1, 50.37, 0.00, 0.00, 0), -(103, '2026-02-27 11:28:00', 74.13, 'Card', 10, 2, 10, 1, 8, 'IN_STORE', NULL, 1, 74.13, 0.00, 0.00, 0), -(104, '2026-03-01 11:39:00', 68.78, 'Card', 11, 3, 11, 1, 9, 'IN_STORE', NULL, 1, 68.78, 0.00, 0.00, 0), -(105, '2026-03-03 11:50:00', 17.89, 'Card', 12, 3, 12, 1, 10, 'IN_STORE', NULL, 1, 17.89, 0.00, 0.00, 0), -(106, '2026-03-05 12:01:00', 63.09, 'Card', 13, 3, 13, 1, 11, 'IN_STORE', NULL, 1, 63.09, 0.00, 0.00, 0), -(107, '2026-03-07 12:12:00', 149.99, 'Card', 14, 3, 14, 1, 12, 'IN_STORE', NULL, 1, 149.99, 0.00, 0.00, 0), -(108, '2026-01-28 09:15:00', 41.36, 'Card', 3, 1, 15, 1, 13, 'IN_STORE', NULL, 1, 41.36, 0.00, 0.00, 0), -(109, '2026-01-29 09:52:00', 68.47, 'Card', 8, 2, 16, 1, 14, 'IN_STORE', NULL, 1, 68.47, 0.00, 0.00, 0), -(110, '2026-01-31 10:29:00', 14.16, 'Card', 13, 3, 17, 1, 15, 'IN_STORE', NULL, 1, 14.16, 0.00, 0.00, 0); +(1, '2026-02-02 10:11:00', 37.83, 'Cash', 3, 1, 3, 0, NULL, 'Online', 1, 1, 44.51, 0.00, 6.68, 0), +(2, '2026-02-03 10:22:00', 192.78, 'Card', 4, 1, 4, 0, NULL, 'Online', 2, 2, 252.00, 25.20, 34.02, 0), +(3, '2026-02-04 10:33:00', 363.23, 'Card', 5, 1, 5, 0, NULL, 'Online', 3, 3, 432.33, 5.00, 64.10, 0), +(4, '2026-02-05 10:44:00', 81.29, 'Cash', 6, 1, 6, 0, NULL, 'Online', 4, 7, 108.67, 13.04, 14.34, 0), +(5, '2026-02-06 10:55:00', 435.73, 'Card', 7, 2, 7, 0, NULL, 'Online', 5, 1, 512.62, 0.00, 76.89, 0), +(6, '2026-02-07 11:06:00', 409.56, 'Card', 8, 2, 8, 0, NULL, 'Online', 6, 1, 481.83, 0.00, 72.27, 0), +(7, '2026-02-08 11:17:00', 56.40, 'Cash', 9, 2, 9, 0, NULL, 'Online', 7, 4, 78.06, 11.71, 9.95, 0), +(8, '2026-02-09 11:28:00', 174.20, 'Card', 10, 2, 10, 0, NULL, 'Online', 8, 5, 212.94, 8.00, 30.74, 0), +(9, '2026-02-10 11:39:00', 619.91, 'Card', 11, 3, 11, 0, NULL, 'Online', 9, 1, 729.31, 0.00, 109.40, 0), +(10, '2026-02-11 11:50:00', 169.73, 'Card', 12, 3, 12, 0, NULL, 'Online', 10, 6, 221.87, 22.19, 29.95, 0), +(11, '2026-02-12 12:01:00', 137.86, 'Cash', 13, 3, 13, 0, NULL, 'Online', 11, 1, 162.19, 0.00, 24.33, 0), +(12, '2026-02-13 12:12:00', 453.95, 'Card', 14, 3, 14, 0, NULL, 'Online', 12, 2, 593.40, 59.34, 80.11, 0), +(13, '2026-01-05 09:15:00', 82.72, 'Card', 3, 1, 15, 0, NULL, 'In Store', NULL, 1, 82.72, 0.00, 0.00, 0), +(14, '2026-01-05 09:52:00', 120.43, 'Card', 8, 2, 16, 0, NULL, 'In Store', NULL, 2, 133.81, 13.38, 0.00, 12), +(15, '2026-01-06 10:29:00', 153.21, 'Cash', 13, 3, 17, 0, NULL, 'In Store', NULL, 1, 153.21, 0.00, 0.00, 15), +(16, '2026-01-06 11:06:00', 20.27, 'Card', 6, 1, 18, 0, NULL, 'In Store', NULL, 3, 25.27, 5.00, 0.00, 2), +(17, '2026-01-07 11:43:00', 58.96, 'Cash', 7, 2, 19, 0, NULL, 'In Store', NULL, 1, 58.96, 0.00, 0.00, 5), +(18, '2026-01-07 12:20:00', 124.54, 'Card', 12, 3, 20, 0, NULL, 'In Store', NULL, 7, 141.52, 16.98, 0.00, 12), +(19, '2026-01-08 12:57:00', 118.84, 'Card', 5, 1, 21, 0, NULL, 'In Store', NULL, 1, 118.84, 0.00, 0.00, 11), +(20, '2026-01-08 13:34:00', 167.02, 'Cash', 10, 2, 22, 0, NULL, 'In Store', NULL, 4, 196.50, 29.48, 0.00, 16), +(21, '2026-01-09 14:11:00', 367.69, 'Card', 11, 3, 23, 0, NULL, 'In Store', NULL, 1, 367.69, 0.00, 0.00, 36), +(22, '2026-01-09 14:48:00', 57.62, 'Cash', 4, 1, 24, 0, NULL, 'In Store', NULL, 1, 57.62, 0.00, 0.00, 5), +(23, '2026-01-10 15:25:00', 84.03, 'Card', 9, 2, 25, 0, NULL, 'In Store', NULL, 6, 93.37, 9.34, 0.00, 8), +(24, '2026-01-10 16:02:00', 297.25, 'Card', 14, 3, 26, 0, NULL, 'In Store', NULL, 1, 297.25, 0.00, 0.00, 29), +(25, '2026-01-11 16:39:00', 35.78, 'Cash', 3, 1, 27, 0, NULL, 'In Store', NULL, 2, 35.78, 0.00, 0.00, 3), +(26, '2026-01-11 17:16:00', 136.99, 'Card', 8, 2, 28, 0, NULL, 'In Store', NULL, 1, 136.99, 0.00, 0.00, 13), +(27, '2026-01-12 17:53:00', 300.52, 'Cash', 13, 3, 29, 0, NULL, 'In Store', NULL, 3, 305.52, 5.00, 0.00, 30), +(28, '2026-01-12 18:30:00', 165.99, 'Card', 6, 1, 30, 0, NULL, 'In Store', NULL, 1, 165.99, 0.00, 0.00, 16), +(29, '2026-01-13 19:07:00', 91.28, 'Card', 7, 2, 31, 0, NULL, 'In Store', NULL, 7, 103.73, 12.45, 0.00, 9), +(30, '2026-01-13 19:44:00', 198.88, 'Cash', 12, 3, 32, 0, NULL, 'In Store', NULL, 1, 198.88, 0.00, 0.00, 19), +(31, '2026-01-14 20:21:00', 25.28, 'Card', 5, 1, 33, 0, NULL, 'In Store', NULL, 4, 25.28, 0.00, 0.00, 2), +(32, '2026-01-14 20:58:00', 58.51, 'Cash', 10, 2, 34, 0, NULL, 'In Store', NULL, 1, 58.51, 0.00, 0.00, 5), +(33, '2026-01-15 21:35:00', 314.15, 'Card', 11, 3, 35, 0, NULL, 'In Store', NULL, 1, 314.15, 0.00, 0.00, 31), +(34, '2026-01-15 22:12:00', 61.62, 'Card', 4, 1, 36, 0, NULL, 'In Store', NULL, 6, 68.47, 6.85, 0.00, 6), +(35, '2026-01-16 22:49:00', 49.61, 'Cash', 9, 2, 37, 0, NULL, 'In Store', NULL, 1, 49.61, 0.00, 0.00, 4), +(36, '2026-01-16 23:26:00', 196.32, 'Card', 14, 3, 38, 0, NULL, 'In Store', NULL, 2, 218.13, 21.81, 0.00, 19), +(37, '2026-01-18 00:03:00', 47.92, 'Cash', 3, 1, 39, 0, NULL, 'In Store', NULL, 1, 47.92, 0.00, 0.00, 4), +(38, '2026-01-18 00:40:00', 121.03, 'Card', 8, 2, 40, 0, NULL, 'In Store', NULL, 3, 126.03, 5.00, 0.00, 12), +(39, '2026-01-19 01:17:00', 187.91, 'Card', 13, 3, 41, 0, NULL, 'In Store', NULL, 1, 187.91, 0.00, 0.00, 18), +(40, '2026-01-19 01:54:00', 108.22, 'Cash', 6, 1, 42, 0, NULL, 'In Store', NULL, 7, 122.98, 14.76, 0.00, 10), +(41, '2026-01-20 02:31:00', 67.71, 'Card', 7, 2, 43, 0, NULL, 'In Store', NULL, 1, 67.71, 0.00, 0.00, 6), +(42, '2026-01-20 03:08:00', 114.93, 'Cash', 12, 3, 44, 0, NULL, 'In Store', NULL, 4, 135.21, 20.28, 0.00, 11), +(43, '2026-01-21 03:45:00', 55.38, 'Card', 5, 1, 45, 0, NULL, 'In Store', NULL, 1, 55.38, 0.00, 0.00, 5), +(44, '2026-01-21 04:22:00', 286.34, 'Card', 10, 2, 46, 0, NULL, 'In Store', NULL, 1, 286.34, 0.00, 0.00, 28), +(45, '2026-01-22 04:59:00', 83.62, 'Cash', 11, 3, 47, 0, NULL, 'In Store', NULL, 6, 92.91, 9.29, 0.00, 8), +(46, '2026-01-22 05:36:00', 29.89, 'Card', 4, 1, 48, 0, NULL, 'In Store', NULL, 1, 29.89, 0.00, 0.00, 2), +(47, '2026-01-23 06:13:00', 161.48, 'Cash', 9, 2, 49, 0, NULL, 'In Store', NULL, 2, 179.42, 17.94, 0.00, 16), +(48, '2026-01-23 06:50:00', 210.14, 'Card', 14, 3, 50, 0, NULL, 'In Store', NULL, 1, 210.14, 0.00, 0.00, 21), +(49, '2026-01-24 07:27:00', 73.64, 'Card', 3, 1, 51, 0, NULL, 'In Store', NULL, 3, 78.64, 5.00, 0.00, 7), +(50, '2026-01-24 08:04:00', 179.28, 'Cash', 8, 2, 52, 0, NULL, 'In Store', NULL, 1, 179.28, 0.00, 0.00, 17), +(51, '2026-01-25 08:41:00', 101.67, 'Card', 13, 3, 53, 0, NULL, 'In Store', NULL, 7, 115.53, 13.86, 0.00, 10), +(52, '2026-01-25 09:18:00', 21.31, 'Cash', 6, 1, 54, 0, NULL, 'In Store', NULL, 1, 21.31, 0.00, 0.00, 2), +(53, '2026-01-26 09:55:00', 79.57, 'Card', 7, 2, 55, 0, NULL, 'In Store', NULL, 4, 93.61, 14.04, 0.00, 7), +(54, '2026-01-26 10:32:00', 156.49, 'Card', 12, 3, 56, 0, NULL, 'In Store', NULL, 1, 156.49, 0.00, 0.00, 15), +(55, '2026-01-27 11:09:00', 28.32, 'Cash', 5, 1, 57, 0, NULL, 'In Store', NULL, 1, 28.32, 0.00, 0.00, 2), +(56, '2026-01-27 11:46:00', 175.47, 'Card', 10, 2, 58, 0, NULL, 'In Store', NULL, 6, 194.97, 19.50, 0.00, 17), +(57, '2026-01-28 12:23:00', 150.60, 'Cash', 11, 3, 59, 0, NULL, 'In Store', NULL, 1, 150.60, 0.00, 0.00, 15), +(58, '2026-01-28 13:00:00', 45.73, 'Card', 4, 1, 60, 0, NULL, 'In Store', NULL, 2, 45.73, 0.00, 0.00, 4), +(59, '2026-01-29 13:37:00', 132.93, 'Card', 9, 2, 61, 0, NULL, 'In Store', NULL, 1, 132.93, 0.00, 0.00, 13), +(60, '2026-01-29 14:14:00', 261.49, 'Cash', 14, 3, 62, 0, NULL, 'In Store', NULL, 3, 266.49, 5.00, 0.00, 26), +(61, '2026-01-30 14:51:00', 57.02, 'Card', 3, 1, 63, 0, NULL, 'In Store', NULL, 1, 57.02, 0.00, 0.00, 5), +(62, '2026-01-30 15:28:00', 90.64, 'Cash', 8, 2, 64, 0, NULL, 'In Store', NULL, 7, 103.00, 12.36, 0.00, 9), +(63, '2026-01-31 16:05:00', 228.91, 'Card', 13, 3, 65, 0, NULL, 'In Store', NULL, 1, 228.91, 0.00, 0.00, 22), +(64, '2026-01-31 16:42:00', 50.36, 'Card', 6, 1, 66, 0, NULL, 'In Store', NULL, 4, 50.36, 0.00, 0.00, 5), +(65, '2026-02-01 17:19:00', 53.77, 'Cash', 7, 2, 67, 0, NULL, 'In Store', NULL, 1, 53.77, 0.00, 0.00, 5), +(66, '2026-02-01 17:56:00', 172.92, 'Card', 12, 3, 68, 0, NULL, 'In Store', NULL, 1, 172.92, 0.00, 0.00, 17), +(67, '2026-02-02 18:33:00', 154.78, 'Cash', 5, 1, 69, 0, NULL, 'In Store', NULL, 6, 171.98, 17.20, 0.00, 15), +(68, '2026-02-02 19:10:00', 198.21, 'Card', 10, 2, 70, 0, NULL, 'In Store', NULL, 1, 198.21, 0.00, 0.00, 19), +(69, '2026-02-03 19:47:00', 134.85, 'Card', 11, 3, 71, 0, NULL, 'In Store', NULL, 2, 149.83, 14.98, 0.00, 13), +(70, '2026-02-03 20:24:00', 74.88, 'Cash', 4, 1, 72, 0, NULL, 'In Store', NULL, 1, 74.88, 0.00, 0.00, 7), +(71, '2026-02-04 21:01:00', 27.77, 'Card', 9, 2, 73, 0, NULL, 'In Store', NULL, 3, 32.77, 5.00, 0.00, 2), +(72, '2026-02-04 21:38:00', 105.22, 'Cash', 14, 3, 74, 0, NULL, 'In Store', NULL, 1, 105.22, 0.00, 0.00, 10), +(73, '2026-02-05 22:15:00', 72.79, 'Card', 3, 1, 75, 0, NULL, 'In Store', NULL, 7, 82.72, 9.93, 0.00, 7), +(74, '2026-02-05 22:52:00', 133.81, 'Card', 8, 2, 76, 0, NULL, 'In Store', NULL, 1, 133.81, 0.00, 0.00, 13), +(75, '2026-02-06 23:29:00', 130.23, 'Cash', 13, 3, 77, 0, NULL, 'In Store', NULL, 4, 153.21, 22.98, 0.00, 13), +(76, '2026-02-07 00:06:00', 25.27, 'Card', 6, 1, 78, 0, NULL, 'In Store', NULL, 1, 25.27, 0.00, 0.00, 2), +(77, '2026-02-08 00:43:00', 58.96, 'Cash', 7, 2, 79, 0, NULL, 'In Store', NULL, 1, 58.96, 0.00, 0.00, 5), +(78, '2026-02-08 01:20:00', 127.37, 'Card', 12, 3, 80, 0, NULL, 'In Store', NULL, 6, 141.52, 14.15, 0.00, 12), +(79, '2026-02-09 01:57:00', 118.84, 'Card', 5, 1, 81, 0, NULL, 'In Store', NULL, 1, 118.84, 0.00, 0.00, 11), +(80, '2026-02-09 02:34:00', 176.85, 'Cash', 10, 2, 82, 0, NULL, 'In Store', NULL, 2, 196.50, 19.65, 0.00, 17), +(81, '2026-02-10 03:11:00', 367.69, 'Card', 11, 3, 83, 0, NULL, 'In Store', NULL, 1, 367.69, 0.00, 0.00, 36), +(82, '2026-02-10 03:48:00', 52.62, 'Cash', 4, 1, 84, 0, NULL, 'In Store', NULL, 3, 57.62, 5.00, 0.00, 5), +(83, '2026-02-11 04:25:00', 93.37, 'Card', 9, 2, 85, 0, NULL, 'In Store', NULL, 1, 93.37, 0.00, 0.00, 9), +(84, '2026-02-11 05:02:00', 261.58, 'Card', 14, 3, 86, 0, NULL, 'In Store', NULL, 7, 297.25, 35.67, 0.00, 26), +(85, '2026-02-12 05:39:00', 35.78, 'Cash', 3, 1, 87, 0, NULL, 'In Store', NULL, 1, 35.78, 0.00, 0.00, 3), +(86, '2026-02-12 06:16:00', 116.44, 'Card', 8, 2, 88, 0, NULL, 'In Store', NULL, 4, 136.99, 20.55, 0.00, 11), +(87, '2026-02-13 06:53:00', 305.52, 'Cash', 13, 3, 89, 0, NULL, 'In Store', NULL, 1, 305.52, 0.00, 0.00, 30), +(88, '2026-02-13 07:30:00', 165.99, 'Card', 6, 1, 90, 0, NULL, 'In Store', NULL, 1, 165.99, 0.00, 0.00, 16), +(89, '2026-02-14 08:07:00', 93.36, 'Card', 7, 2, 91, 0, NULL, 'In Store', NULL, 6, 103.73, 10.37, 0.00, 9), +(90, '2026-02-14 08:44:00', 198.88, 'Cash', 12, 3, 92, 0, NULL, 'In Store', NULL, 1, 198.88, 0.00, 0.00, 19), +(91, '2026-02-15 09:21:00', 25.28, 'Card', 5, 1, 93, 0, NULL, 'In Store', NULL, 2, 25.28, 0.00, 0.00, 2), +(92, '2026-02-15 09:58:00', 58.51, 'Cash', 10, 2, 94, 0, NULL, 'In Store', NULL, 1, 58.51, 0.00, 0.00, 5), +(93, '2026-02-16 10:35:00', 309.15, 'Card', 11, 3, 95, 0, NULL, 'In Store', NULL, 3, 314.15, 5.00, 0.00, 30), +(94, '2026-02-16 11:12:00', 68.47, 'Card', 4, 1, 96, 0, NULL, 'In Store', NULL, 1, 68.47, 0.00, 0.00, 6), +(95, '2026-02-17 11:49:00', 49.61, 'Cash', 9, 2, 97, 0, NULL, 'In Store', NULL, 7, 49.61, 0.00, 0.00, 4), +(96, '2026-02-13 10:11:00', 34.80, 'Card', 3, 1, 3, 1, 1, 'In Store', NULL, 1, 34.80, 0.00, 0.00, 0), +(97, '2026-02-15 10:22:00', 88.32, 'Card', 4, 1, 4, 1, 2, 'In Store', NULL, 1, 88.32, 0.00, 0.00, 0), +(98, '2026-02-17 10:33:00', 49.05, 'Card', 5, 1, 5, 1, 3, 'In Store', NULL, 1, 49.05, 0.00, 0.00, 0), +(99, '2026-02-19 10:44:00', 15.25, 'Card', 6, 1, 6, 1, 4, 'In Store', NULL, 1, 15.25, 0.00, 0.00, 0), +(100, '2026-02-21 10:55:00', 181.42, 'Card', 7, 2, 7, 1, 5, 'In Store', NULL, 1, 181.42, 0.00, 0.00, 0), +(101, '2026-02-23 11:06:00', 130.82, 'Card', 8, 2, 8, 1, 6, 'In Store', NULL, 1, 130.82, 0.00, 0.00, 0), +(102, '2026-02-25 11:17:00', 50.37, 'Card', 9, 2, 9, 1, 7, 'In Store', NULL, 1, 50.37, 0.00, 0.00, 0), +(103, '2026-02-27 11:28:00', 74.13, 'Card', 10, 2, 10, 1, 8, 'In Store', NULL, 1, 74.13, 0.00, 0.00, 0), +(104, '2026-03-01 11:39:00', 68.78, 'Card', 11, 3, 11, 1, 9, 'In Store', NULL, 1, 68.78, 0.00, 0.00, 0), +(105, '2026-03-03 11:50:00', 17.89, 'Card', 12, 3, 12, 1, 10, 'In Store', NULL, 1, 17.89, 0.00, 0.00, 0), +(106, '2026-03-05 12:01:00', 63.09, 'Card', 13, 3, 13, 1, 11, 'In Store', NULL, 1, 63.09, 0.00, 0.00, 0), +(107, '2026-03-07 12:12:00', 149.99, 'Card', 14, 3, 14, 1, 12, 'In Store', NULL, 1, 149.99, 0.00, 0.00, 0), +(108, '2026-01-28 09:15:00', 41.36, 'Card', 3, 1, 15, 1, 13, 'In Store', NULL, 1, 41.36, 0.00, 0.00, 0), +(109, '2026-01-29 09:52:00', 68.47, 'Card', 8, 2, 16, 1, 14, 'In Store', NULL, 1, 68.47, 0.00, 0.00, 0), +(110, '2026-01-31 10:29:00', 14.16, 'Card', 13, 3, 17, 1, 15, 'In Store', NULL, 1, 14.16, 0.00, 0.00, 0); INSERT INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES (1, 1, 1, 1, 25.09), @@ -2259,52 +2259,52 @@ SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/stores/', '/store WHERE imageUrl LIKE 'https://images.petshop.local/stores/%'; INSERT IGNORE INTO appointment (appointmentId, serviceId, petId, customerId, storeId, employeeId, appointmentDate, appointmentTime, appointmentStatus) VALUES -(91, 7, 37, 16, 1, 3, '2026-03-08', '09:00:00', 'COMPLETED'), -(92, 8, 38, 17, 2, 8, '2026-03-10', '10:30:00', 'COMPLETED'), -(93, 8, 39, 18, 3, 13, '2026-03-12', '13:00:00', 'MISSED'), -(94, 4, 40, 19, 1, 6, '2026-03-14', '14:30:00', 'COMPLETED'), -(95, 5, 41, 20, 2, 7, '2026-03-16', '09:00:00', 'COMPLETED'), -(96, 8, 42, 21, 3, 12, '2026-03-18', '10:30:00', 'COMPLETED'), -(97, 2, 43, 22, 1, 5, '2026-03-20', '13:00:00', 'CANCELLED'), -(98, 8, 44, 23, 2, 10, '2026-03-22', '14:30:00', 'COMPLETED'), -(99, 8, 45, 24, 3, 11, '2026-03-24', '09:00:00', 'COMPLETED'), -(100, 8, 46, 25, 1, 4, '2026-03-26', '10:30:00', 'MISSED'), -(101, 6, 47, 26, 2, 9, '2026-03-28', '13:00:00', 'COMPLETED'), -(102, 4, 48, 27, 3, 14, '2026-03-30', '14:30:00', 'COMPLETED'), -(103, 7, 49, 28, 1, 3, '2026-04-01', '09:00:00', 'COMPLETED'), -(104, 6, 50, 29, 2, 8, '2026-04-03', '10:30:00', 'COMPLETED'), -(105, 1, 51, 30, 3, 13, '2026-04-05', '13:00:00', 'MISSED'), -(106, 4, 52, 31, 1, 6, '2026-04-07', '14:30:00', 'COMPLETED'), -(107, 1, 53, 32, 2, 7, '2026-04-09', '09:00:00', 'COMPLETED'), -(108, 2, 54, 33, 3, 12, '2026-04-11', '10:30:00', 'CANCELLED'), -(109, 3, 55, 34, 1, 5, '2026-04-13', '13:00:00', 'COMPLETED'), -(110, 4, 56, 35, 2, 10, '2026-04-15', '10:00:00', 'SCHEDULED'), -(111, 5, 57, 36, 3, 11, '2026-04-16', '14:00:00', 'SCHEDULED'); +(91, 7, 37, 16, 1, 3, '2026-03-08', '09:00:00', 'Completed'), +(92, 8, 38, 17, 2, 8, '2026-03-10', '10:30:00', 'Completed'), +(93, 8, 39, 18, 3, 13, '2026-03-12', '13:00:00', 'Missed'), +(94, 4, 40, 19, 1, 6, '2026-03-14', '14:30:00', 'Completed'), +(95, 5, 41, 20, 2, 7, '2026-03-16', '09:00:00', 'Completed'), +(96, 8, 42, 21, 3, 12, '2026-03-18', '10:30:00', 'Completed'), +(97, 2, 43, 22, 1, 5, '2026-03-20', '13:00:00', 'Cancelled'), +(98, 8, 44, 23, 2, 10, '2026-03-22', '14:30:00', 'Completed'), +(99, 8, 45, 24, 3, 11, '2026-03-24', '09:00:00', 'Completed'), +(100, 8, 46, 25, 1, 4, '2026-03-26', '10:30:00', 'Missed'), +(101, 6, 47, 26, 2, 9, '2026-03-28', '13:00:00', 'Completed'), +(102, 4, 48, 27, 3, 14, '2026-03-30', '14:30:00', 'Completed'), +(103, 7, 49, 28, 1, 3, '2026-04-01', '09:00:00', 'Completed'), +(104, 6, 50, 29, 2, 8, '2026-04-03', '10:30:00', 'Completed'), +(105, 1, 51, 30, 3, 13, '2026-04-05', '13:00:00', 'Missed'), +(106, 4, 52, 31, 1, 6, '2026-04-07', '14:30:00', 'Completed'), +(107, 1, 53, 32, 2, 7, '2026-04-09', '09:00:00', 'Completed'), +(108, 2, 54, 33, 3, 12, '2026-04-11', '10:30:00', 'Cancelled'), +(109, 3, 55, 34, 1, 5, '2026-04-13', '13:00:00', 'Completed'), +(110, 4, 56, 35, 2, 10, '2026-04-15', '10:00:00', 'Scheduled'), +(111, 5, 57, 36, 3, 11, '2026-04-16', '14:00:00', 'Scheduled'); INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES -(111, '2026-03-08 09:15:00', 87.50, 'Cash', 3, 1, 3, 0, NULL, 'IN_STORE', NULL, NULL, 87.50, 0.00, 0.00, 8), -(112, '2026-03-09 10:22:00', 145.20, 'Card', 8, 2, 4, 0, NULL, 'IN_STORE', NULL, NULL, 145.20, 0.00, 0.00, 14), -(113, '2026-03-10 11:33:00', 63.75, 'Cash', 13, 3, 5, 0, NULL, 'IN_STORE', NULL, NULL, 63.75, 0.00, 0.00, 6), -(114, '2026-03-11 12:44:00', 210.00, 'Card', 6, 1, 6, 0, NULL, 'ONLINE', NULL, NULL, 210.00, 0.00, 0.00, 21), -(115, '2026-03-12 13:55:00', 38.90, 'Cash', 7, 2, 7, 0, NULL, 'IN_STORE', NULL, NULL, 38.90, 0.00, 0.00, 3), -(116, '2026-03-14 09:10:00', 325.40, 'Card', 12, 3, 8, 0, NULL, 'ONLINE', NULL, NULL, 325.40, 0.00, 0.00, 32), -(117, '2026-03-16 10:25:00', 72.15, 'Cash', 5, 1, 9, 0, NULL, 'IN_STORE', NULL, NULL, 72.15, 0.00, 0.00, 7), -(118, '2026-03-18 11:40:00', 190.80, 'Card', 10, 2, 10, 0, NULL, 'ONLINE', NULL, NULL, 190.80, 0.00, 0.00, 19), -(119, '2026-03-20 12:55:00', 55.30, 'Cash', 11, 3, 11, 0, NULL, 'IN_STORE', NULL, NULL, 55.30, 0.00, 0.00, 5), -(120, '2026-03-22 14:10:00', 412.60, 'Card', 4, 1, 12, 0, NULL, 'ONLINE', NULL, NULL, 412.60, 0.00, 0.00, 41), -(121, '2026-03-24 09:30:00', 98.45, 'Cash', 9, 2, 13, 0, NULL, 'IN_STORE', NULL, NULL, 98.45, 0.00, 0.00, 9), -(122, '2026-03-26 10:45:00', 167.70, 'Card', 14, 3, 14, 0, NULL, 'ONLINE', NULL, NULL, 167.70, 0.00, 0.00, 16), -(123, '2026-03-28 12:00:00', 44.20, 'Cash', 3, 1, 15, 0, NULL, 'IN_STORE', NULL, NULL, 44.20, 0.00, 0.00, 4), -(124, '2026-03-30 13:15:00', 289.55, 'Card', 8, 2, 16, 0, NULL, 'ONLINE', NULL, NULL, 289.55, 0.00, 0.00, 28), -(125, '2026-04-01 09:20:00', 76.80, 'Cash', 13, 3, 17, 0, NULL, 'IN_STORE', NULL, NULL, 76.80, 0.00, 0.00, 7), -(126, '2026-04-03 10:35:00', 234.10, 'Card', 6, 1, 18, 0, NULL, 'ONLINE', NULL, NULL, 234.10, 0.00, 0.00, 23), -(127, '2026-04-05 11:50:00', 52.40, 'Cash', 7, 2, 19, 0, NULL, 'IN_STORE', NULL, NULL, 52.40, 0.00, 0.00, 5), -(128, '2026-04-07 13:05:00', 178.90, 'Card', 12, 3, 20, 0, NULL, 'ONLINE', NULL, NULL, 178.90, 0.00, 0.00, 17), -(129, '2026-04-09 09:15:00', 115.60, 'Cash', 5, 1, 21, 0, NULL, 'IN_STORE', NULL, NULL, 115.60, 0.00, 0.00, 11), -(130, '2026-04-11 10:30:00', 367.25, 'Card', 10, 2, 22, 0, NULL, 'ONLINE', NULL, NULL, 367.25, 0.00, 0.00, 36), -(131, '2026-04-14 11:45:00', 89.70, 'Cash', 11, 3, 23, 0, NULL, 'IN_STORE', NULL, NULL, 89.70, 0.00, 0.00, 8), -(132, '2026-04-15 09:00:00', 145.30, 'Card', 4, 1, 24, 0, NULL, 'ONLINE', NULL, NULL, 145.30, 0.00, 0.00, 14), -(133, '2026-04-16 10:00:00', 78.60, 'Cash', 9, 2, 25, 0, NULL, 'IN_STORE', NULL, NULL, 78.60, 0.00, 0.00, 7); +(111, '2026-03-08 09:15:00', 87.50, 'Cash', 3, 1, 3, 0, NULL, 'In Store', NULL, NULL, 87.50, 0.00, 0.00, 8), +(112, '2026-03-09 10:22:00', 145.20, 'Card', 8, 2, 4, 0, NULL, 'In Store', NULL, NULL, 145.20, 0.00, 0.00, 14), +(113, '2026-03-10 11:33:00', 63.75, 'Cash', 13, 3, 5, 0, NULL, 'In Store', NULL, NULL, 63.75, 0.00, 0.00, 6), +(114, '2026-03-11 12:44:00', 210.00, 'Card', 6, 1, 6, 0, NULL, 'Online', NULL, NULL, 210.00, 0.00, 0.00, 21), +(115, '2026-03-12 13:55:00', 38.90, 'Cash', 7, 2, 7, 0, NULL, 'In Store', NULL, NULL, 38.90, 0.00, 0.00, 3), +(116, '2026-03-14 09:10:00', 325.40, 'Card', 12, 3, 8, 0, NULL, 'Online', NULL, NULL, 325.40, 0.00, 0.00, 32), +(117, '2026-03-16 10:25:00', 72.15, 'Cash', 5, 1, 9, 0, NULL, 'In Store', NULL, NULL, 72.15, 0.00, 0.00, 7), +(118, '2026-03-18 11:40:00', 190.80, 'Card', 10, 2, 10, 0, NULL, 'Online', NULL, NULL, 190.80, 0.00, 0.00, 19), +(119, '2026-03-20 12:55:00', 55.30, 'Cash', 11, 3, 11, 0, NULL, 'In Store', NULL, NULL, 55.30, 0.00, 0.00, 5), +(120, '2026-03-22 14:10:00', 412.60, 'Card', 4, 1, 12, 0, NULL, 'Online', NULL, NULL, 412.60, 0.00, 0.00, 41), +(121, '2026-03-24 09:30:00', 98.45, 'Cash', 9, 2, 13, 0, NULL, 'In Store', NULL, NULL, 98.45, 0.00, 0.00, 9), +(122, '2026-03-26 10:45:00', 167.70, 'Card', 14, 3, 14, 0, NULL, 'Online', NULL, NULL, 167.70, 0.00, 0.00, 16), +(123, '2026-03-28 12:00:00', 44.20, 'Cash', 3, 1, 15, 0, NULL, 'In Store', NULL, NULL, 44.20, 0.00, 0.00, 4), +(124, '2026-03-30 13:15:00', 289.55, 'Card', 8, 2, 16, 0, NULL, 'Online', NULL, NULL, 289.55, 0.00, 0.00, 28), +(125, '2026-04-01 09:20:00', 76.80, 'Cash', 13, 3, 17, 0, NULL, 'In Store', NULL, NULL, 76.80, 0.00, 0.00, 7), +(126, '2026-04-03 10:35:00', 234.10, 'Card', 6, 1, 18, 0, NULL, 'Online', NULL, NULL, 234.10, 0.00, 0.00, 23), +(127, '2026-04-05 11:50:00', 52.40, 'Cash', 7, 2, 19, 0, NULL, 'In Store', NULL, NULL, 52.40, 0.00, 0.00, 5), +(128, '2026-04-07 13:05:00', 178.90, 'Card', 12, 3, 20, 0, NULL, 'Online', NULL, NULL, 178.90, 0.00, 0.00, 17), +(129, '2026-04-09 09:15:00', 115.60, 'Cash', 5, 1, 21, 0, NULL, 'In Store', NULL, NULL, 115.60, 0.00, 0.00, 11), +(130, '2026-04-11 10:30:00', 367.25, 'Card', 10, 2, 22, 0, NULL, 'Online', NULL, NULL, 367.25, 0.00, 0.00, 36), +(131, '2026-04-14 11:45:00', 89.70, 'Cash', 11, 3, 23, 0, NULL, 'In Store', NULL, NULL, 89.70, 0.00, 0.00, 8), +(132, '2026-04-15 09:00:00', 145.30, 'Card', 4, 1, 24, 0, NULL, 'Online', NULL, NULL, 145.30, 0.00, 0.00, 14), +(133, '2026-04-16 10:00:00', 78.60, 'Cash', 9, 2, 25, 0, NULL, 'In Store', NULL, NULL, 78.60, 0.00, 0.00, 7); INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES (226, 111, 5, 2, 25.50), @@ -2452,30 +2452,30 @@ INSERT INTO activityLog (userId, storeId, usernameSnapshot, fullNameSnapshot, ro (11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-04-19 11:00:22'); INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES -(134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'IN_STORE', NULL, NULL, 57.67, 0.00, 0.00, 5), -(135, '2026-04-10 11:30:00', 143.55, 'Card', 8, 2, 17, 0, NULL, 'ONLINE', NULL, NULL, 143.55, 0.00, 0.00, 14), -(136, '2026-04-10 14:45:00', 50.09, 'Cash', 12, 3, 18, 0, NULL, 'IN_STORE', NULL, NULL, 50.09, 0.00, 0.00, 5), -(137, '2026-04-11 10:00:00', 114.48, 'Card', 5, 1, 19, 0, NULL, 'ONLINE', NULL, NULL, 114.48, 0.00, 0.00, 11), -(138, '2026-04-11 13:20:00', 93.55, 'Cash', 9, 2, 20, 0, NULL, 'IN_STORE', NULL, NULL, 93.55, 0.00, 0.00, 9), -(139, '2026-04-12 09:45:00', 100.71, 'Card', 13, 3, 21, 0, NULL, 'ONLINE', NULL, NULL, 100.71, 0.00, 0.00, 10), -(140, '2026-04-12 11:00:00', 51.07, 'Cash', 6, 1, 22, 0, NULL, 'IN_STORE', NULL, NULL, 51.07, 0.00, 0.00, 5), -(141, '2026-04-12 15:30:00', 139.66, 'Card', 7, 2, 23, 0, NULL, 'ONLINE', NULL, NULL, 139.66, 0.00, 0.00, 13), -(142, '2026-04-13 09:00:00', 73.98, 'Cash', 14, 3, 24, 0, NULL, 'IN_STORE', NULL, NULL, 73.98, 0.00, 0.00, 7), -(143, '2026-04-13 12:15:00', 134.76, 'Card', 4, 1, 25, 0, NULL, 'ONLINE', NULL, NULL, 134.76, 0.00, 0.00, 13), -(144, '2026-04-14 10:30:00', 80.40, 'Cash', 10, 2, 26, 0, NULL, 'IN_STORE', NULL, NULL, 80.40, 0.00, 0.00, 8), -(145, '2026-04-14 14:00:00', 125.90, 'Card', 11, 3, 27, 0, NULL, 'ONLINE', NULL, NULL, 125.90, 0.00, 0.00, 12), -(146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'IN_STORE', NULL, NULL, 80.62, 0.00, 0.00, 8), -(147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'ONLINE', NULL, NULL, 141.28, 0.00, 0.00, 14), -(148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'IN_STORE', NULL, NULL, 97.85, 0.00, 0.00, 9), -(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8), -(150, '2026-04-17 09:15:00', 112.38, 'Cash', 13, 3, 32, 0, NULL, 'IN_STORE', NULL, NULL, 112.38, 0.00, 0.00, 11), -(151, '2026-04-17 11:30:00', 67.49, 'Card', 5, 1, 33, 0, NULL, 'ONLINE', NULL, NULL, 67.49, 0.00, 0.00, 6), -(152, '2026-04-17 14:45:00', 158.20, 'Cash', 9, 2, 34, 0, NULL, 'IN_STORE', NULL, NULL, 158.20, 0.00, 0.00, 15), -(153, '2026-04-18 09:30:00', 84.76, 'Card', 14, 3, 35, 0, NULL, 'ONLINE', NULL, NULL, 84.76, 0.00, 0.00, 8), -(154, '2026-04-18 12:00:00', 203.15, 'Cash', 4, 1, 36, 0, NULL, 'IN_STORE', NULL, NULL, 203.15, 0.00, 0.00, 20), -(155, '2026-04-18 15:15:00', 45.93, 'Card', 7, 2, 37, 0, NULL, 'ONLINE', NULL, NULL, 45.93, 0.00, 0.00, 4), -(156, '2026-04-19 10:00:00', 129.84, 'Cash', 11, 3, 38, 0, NULL, 'IN_STORE', NULL, NULL, 129.84, 0.00, 0.00, 12), -(157, '2026-04-19 13:30:00', 76.50, 'Card', 6, 1, 39, 0, NULL, 'ONLINE', NULL, NULL, 76.50, 0.00, 0.00, 7); +(134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'In Store', NULL, NULL, 57.67, 0.00, 0.00, 5), +(135, '2026-04-10 11:30:00', 143.55, 'Card', 8, 2, 17, 0, NULL, 'Online', NULL, NULL, 143.55, 0.00, 0.00, 14), +(136, '2026-04-10 14:45:00', 50.09, 'Cash', 12, 3, 18, 0, NULL, 'In Store', NULL, NULL, 50.09, 0.00, 0.00, 5), +(137, '2026-04-11 10:00:00', 114.48, 'Card', 5, 1, 19, 0, NULL, 'Online', NULL, NULL, 114.48, 0.00, 0.00, 11), +(138, '2026-04-11 13:20:00', 93.55, 'Cash', 9, 2, 20, 0, NULL, 'In Store', NULL, NULL, 93.55, 0.00, 0.00, 9), +(139, '2026-04-12 09:45:00', 100.71, 'Card', 13, 3, 21, 0, NULL, 'Online', NULL, NULL, 100.71, 0.00, 0.00, 10), +(140, '2026-04-12 11:00:00', 51.07, 'Cash', 6, 1, 22, 0, NULL, 'In Store', NULL, NULL, 51.07, 0.00, 0.00, 5), +(141, '2026-04-12 15:30:00', 139.66, 'Card', 7, 2, 23, 0, NULL, 'Online', NULL, NULL, 139.66, 0.00, 0.00, 13), +(142, '2026-04-13 09:00:00', 73.98, 'Cash', 14, 3, 24, 0, NULL, 'In Store', NULL, NULL, 73.98, 0.00, 0.00, 7), +(143, '2026-04-13 12:15:00', 134.76, 'Card', 4, 1, 25, 0, NULL, 'Online', NULL, NULL, 134.76, 0.00, 0.00, 13), +(144, '2026-04-14 10:30:00', 80.40, 'Cash', 10, 2, 26, 0, NULL, 'In Store', NULL, NULL, 80.40, 0.00, 0.00, 8), +(145, '2026-04-14 14:00:00', 125.90, 'Card', 11, 3, 27, 0, NULL, 'Online', NULL, NULL, 125.90, 0.00, 0.00, 12), +(146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'In Store', NULL, NULL, 80.62, 0.00, 0.00, 8), +(147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'Online', NULL, NULL, 141.28, 0.00, 0.00, 14), +(148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'In Store', NULL, NULL, 97.85, 0.00, 0.00, 9), +(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'Online', NULL, NULL, 89.36, 0.00, 0.00, 8), +(150, '2026-04-17 09:15:00', 112.38, 'Cash', 13, 3, 32, 0, NULL, 'In Store', NULL, NULL, 112.38, 0.00, 0.00, 11), +(151, '2026-04-17 11:30:00', 67.49, 'Card', 5, 1, 33, 0, NULL, 'Online', NULL, NULL, 67.49, 0.00, 0.00, 6), +(152, '2026-04-17 14:45:00', 158.20, 'Cash', 9, 2, 34, 0, NULL, 'In Store', NULL, NULL, 158.20, 0.00, 0.00, 15), +(153, '2026-04-18 09:30:00', 84.76, 'Card', 14, 3, 35, 0, NULL, 'Online', NULL, NULL, 84.76, 0.00, 0.00, 8), +(154, '2026-04-18 12:00:00', 203.15, 'Cash', 4, 1, 36, 0, NULL, 'In Store', NULL, NULL, 203.15, 0.00, 0.00, 20), +(155, '2026-04-18 15:15:00', 45.93, 'Card', 7, 2, 37, 0, NULL, 'Online', NULL, NULL, 45.93, 0.00, 0.00, 4), +(156, '2026-04-19 10:00:00', 129.84, 'Cash', 11, 3, 38, 0, NULL, 'In Store', NULL, NULL, 129.84, 0.00, 0.00, 12), +(157, '2026-04-19 13:30:00', 76.50, 'Card', 6, 1, 39, 0, NULL, 'Online', NULL, NULL, 76.50, 0.00, 0.00, 7); INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES (264, 134, 1, 2, 25.09), -- 2.49.1 From 251c785762b324e7ce131bcc8c1843eb78966f4e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 09:48:53 -0600 Subject: [PATCH 26/42] fix remaining staff roles --- backend/src/main/resources/db/migration/V2__seed_data.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 0a307195..3134a995 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -58,7 +58,7 @@ INSERT INTO storeLocation (storeId, storeName, address, phone, email, imageUrl) (3, 'West Side Store', '789 West Blvd, Calgary, AB', '403-555-0103', 'westside@petshop.com', 'https://images.petshop.local/stores/west.webp'); INSERT INTO users (id, username, password, email, firstName, lastName, fullName, phone, avatarUrl, role, staffRole, primaryStoreId, loyaltyPoints, active, tokenVersion) VALUES -(1, 'admin', '$2y$10$ok/BmOn/pyyamTeNmUDiB.OfLCduQlZSAaRLlupM/cZb7ZhiBriVe', 'admin@petshop.com', 'Admin', 'User', 'Admin User', '000-000-1000', 'https://images.petshop.local/users/001.webp', 'ADMIN', 'ADMINISTRATOR', 1, 0, 1, 0), +(1, 'admin', '$2y$10$ok/BmOn/pyyamTeNmUDiB.OfLCduQlZSAaRLlupM/cZb7ZhiBriVe', 'admin@petshop.com', 'Admin', 'User', 'Admin User', '000-000-1000', 'https://images.petshop.local/users/001.webp', 'ADMIN', 'Administrator', 1, 0, 1, 0), (2, 'morgan.lee', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'morgan.lee@petshop.com', 'Morgan', 'Lee', 'Morgan Lee', '403-700-0002', 'https://images.petshop.local/users/002.webp', 'ADMIN', 'Operations Admin', 2, 0, 1, 0), (3, 'staff', '$2y$10$23mqbLolo609T/.PC4KfiuY.9HqYEgA8LrJ/fccZ7CmK0/OIsPrfq', 'staff@petshop.com', 'Staff', 'User', 'Staff User', '000-000-1001', 'https://images.petshop.local/users/003.webp', 'STAFF', 'Store Manager', 1, 0, 1, 0), (4, 'sara.smith', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sara.smith@petshop.com', 'Sara', 'Smith', 'Sara Smith', '403-710-0004', 'https://images.petshop.local/users/004.webp', 'STAFF', 'Sales Associate', 1, 0, 1, 0), @@ -1763,7 +1763,7 @@ SELECT 'ai.bot', '000-000-0000', 'https://images.petshop.local/users/bot.webp', 'STAFF', - 'CUSTOMER_SERVICE', + 'Customer Service', NULL, 0, 1, -- 2.49.1 From d627272d485c67510dbf9ec5db6a04dc152fb6cc Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 10:10:49 -0600 Subject: [PATCH 27/42] evict cache on logout --- .../main/java/com/petshop/backend/controller/AuthController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index 032518ec..e1d14e67 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -347,6 +347,7 @@ public class AuthController { User user = authHelper.getAuthenticatedUser(); user.setTokenVersion(user.getTokenVersion() + 1); userRepository.save(user); + userAuthCacheService.evict(user.getId()); Map response = new HashMap<>(); response.put("message", "Logged out successfully"); return ResponseEntity.ok(response); -- 2.49.1 From 832d1f2c339c7d2343fc1636cbc25a94a767fcc3 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 10:26:01 -0600 Subject: [PATCH 28/42] validate pet price and species --- .../java/com/petshop/backend/dto/chat/MessageRequest.java | 2 +- .../main/java/com/petshop/backend/dto/pet/PetRequest.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java index 37dcd683..0784054b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java @@ -1,7 +1,7 @@ package com.petshop.backend.dto.chat; public class MessageRequest { - @jakarta.validation.constraints.Size(max = 10000, message = "Message content must not exceed 10000 characters") + @jakarta.validation.constraints.Size(max = 2000, message = "Message content must not exceed 2000 characters") private String content; private String attachmentUrl; private String attachmentName; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java index ff4f9ad2..c22a5da6 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -1,8 +1,11 @@ package com.petshop.backend.dto.pet; +import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import java.math.BigDecimal; import java.util.Objects; @@ -11,6 +14,7 @@ public class PetRequest { private String petName; @NotBlank(message = "Species is required") + @Pattern(regexp = "^(Dog|Cat|Bird|Fish|Rabbit|Hamster|Guinea Pig|Reptile|Other)$", flags = Pattern.Flag.CASE_INSENSITIVE, message = "Species must be Dog, Cat, Bird, Fish, Rabbit, Hamster, Guinea Pig, Reptile, or Other") private String petSpecies; private String petBreed; @@ -21,6 +25,8 @@ public class PetRequest { @NotNull(message = "Status is required") private String petStatus; + @PositiveOrZero(message = "Price must be zero or positive") + @DecimalMax(value = "99999.99", message = "Price must not exceed 99999.99") private BigDecimal petPrice; private Long customerId; -- 2.49.1 From b380681be324d3cbcd992010d73c33e6b08c8a4d Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 10:41:49 -0600 Subject: [PATCH 29/42] auto-refresh product list --- .../controllers/ProductController.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java index 07b94d0a..31f4164c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java @@ -1,5 +1,7 @@ package org.example.petshopdesktop.controllers; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -8,6 +10,7 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.scene.Scene; +import javafx.util.Duration; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.ImageView; @@ -78,8 +81,9 @@ public class ProductController { private TextField txtSearch; //data declaration - private ObservableList data = FXCollections.observableArrayList(); //empty + private ObservableList data = FXCollections.observableArrayList(); private String mode = null; + private Timeline refreshTimer; /** * Set up the table view for products and display it when starting up @@ -130,6 +134,15 @@ public class ProductController { } }); + refreshTimer = new Timeline(new KeyFrame(Duration.seconds(30), e -> applyFilters())); + refreshTimer.setCycleCount(Timeline.INDEFINITE); + refreshTimer.play(); + tvProducts.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene == null && refreshTimer != null) { + refreshTimer.stop(); + } + }); + } /** -- 2.49.1 From a10841dd027e237edd627e25794fd4bcaed07a76 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 10:45:45 -0600 Subject: [PATCH 30/42] add XSS content filter to DTOs --- .../backend/dto/category/CategoryRequest.java | 2 ++ .../petshop/backend/dto/pet/PetRequest.java | 2 ++ .../backend/dto/product/ProductRequest.java | 3 +++ .../backend/dto/service/ServiceRequest.java | 3 +++ .../backend/dto/store/StoreRequest.java | 2 ++ .../backend/dto/supplier/SupplierRequest.java | 1 + .../petshop/backend/dto/user/UserRequest.java | 3 +++ .../com/petshop/backend/util/SafeContent.java | 17 +++++++++++++++++ .../backend/util/SafeContentValidator.java | 18 ++++++++++++++++++ 9 files changed, 51 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/util/SafeContent.java create mode 100644 backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java index c012ae21..b099826f 100644 --- a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -1,10 +1,12 @@ package com.petshop.backend.dto.category; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.NotBlank; import java.util.Objects; public class CategoryRequest { @NotBlank(message = "Category name is required") + @SafeContent private String categoryName; private String categoryType; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java index c22a5da6..4d0986bb 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.pet; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -11,6 +12,7 @@ import java.util.Objects; public class PetRequest { @NotBlank(message = "Pet name is required") + @SafeContent private String petName; @NotBlank(message = "Species is required") diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java index 71dd600f..e1ca9241 100644 --- a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.product; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -8,11 +9,13 @@ import java.util.Objects; public class ProductRequest { @NotBlank(message = "Product name is required") + @SafeContent private String prodName; @NotNull(message = "Category ID is required") private Long categoryId; + @SafeContent private String prodDesc; @NotNull(message = "Product price is required") diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java index 977b72cc..464fefda 100644 --- a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.service; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -10,8 +11,10 @@ import java.util.Set; public class ServiceRequest { @NotBlank(message = "Service name is required") + @SafeContent private String serviceName; + @SafeContent private String serviceDesc; @NotNull(message = "Service price is required") diff --git a/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java b/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java index 5bb68613..fca03db1 100644 --- a/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java @@ -1,11 +1,13 @@ package com.petshop.backend.dto.store; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import java.util.Objects; public class StoreRequest { @NotBlank(message = "Store name is required") + @SafeContent private String storeName; @NotBlank(message = "Address is required") diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index b7ae7efb..a0eb2b6a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.supplier; +import com.petshop.backend.util.SafeContent; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java index 774e9d90..04c82e43 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.user; +import com.petshop.backend.util.SafeContent; import com.petshop.backend.entity.User; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -15,10 +16,12 @@ public class UserRequest { private String password; @NotBlank(message = "First name is required") + @SafeContent @Size(max = 50) private String firstName; @NotBlank(message = "Last name is required") + @SafeContent @Size(max = 50) private String lastName; diff --git a/backend/src/main/java/com/petshop/backend/util/SafeContent.java b/backend/src/main/java/com/petshop/backend/util/SafeContent.java new file mode 100644 index 00000000..6db91b6e --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/util/SafeContent.java @@ -0,0 +1,17 @@ +package com.petshop.backend.util; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = SafeContentValidator.class) +public @interface SafeContent { + String message() default "Content contains prohibited characters or scripts"; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java b/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java new file mode 100644 index 00000000..6d3af630 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java @@ -0,0 +1,18 @@ +package com.petshop.backend.util; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.regex.Pattern; + +public class SafeContentValidator implements ConstraintValidator { + + private static final Pattern DANGEROUS = Pattern.compile( + "]+on\\w+", + Pattern.CASE_INSENSITIVE); + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null || value.isBlank()) return true; + return !DANGEROUS.matcher(value).find(); + } +} -- 2.49.1 From 2628feb5e839d9661adcc8d9f69e7b664897a59f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 11:11:20 -0600 Subject: [PATCH 31/42] delayed refresh after CRUD --- .../controllers/ProductController.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java index 31f4164c..6a66c865 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java @@ -11,6 +11,7 @@ import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.util.Duration; + import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.ImageView; @@ -83,7 +84,6 @@ public class ProductController { //data declaration private ObservableList data = FXCollections.observableArrayList(); private String mode = null; - private Timeline refreshTimer; /** * Set up the table view for products and display it when starting up @@ -134,14 +134,6 @@ public class ProductController { } }); - refreshTimer = new Timeline(new KeyFrame(Duration.seconds(30), e -> applyFilters())); - refreshTimer.setCycleCount(Timeline.INDEFINITE); - refreshTimer.play(); - tvProducts.sceneProperty().addListener((obs, oldScene, newScene) -> { - if (newScene == null && refreshTimer != null) { - refreshTimer.stop(); - } - }); } @@ -236,8 +228,7 @@ public class ProductController { alert.showAndWait(); } - //refresh display and reset inputs - displayProduct(); + displayProductWithFollowUp(); btnDelete.setDisable(true); btnEdit.setDisable(true); txtSearch.setText(""); @@ -263,6 +254,11 @@ public class ProductController { * Filter the table given the string from the searchbar * @param filter word to filter table */ + private void displayProductWithFollowUp() { + displayProduct(); + new Timeline(new KeyFrame(Duration.seconds(2), e -> applyFilters())).play(); + } + private void displayFilteredProduct(String filter){ if ((txtSearch.getText() == null || txtSearch.getText().isEmpty()) && selectedCategoryId() == null){ displayProduct(); @@ -360,8 +356,7 @@ public class ProductController { dialogStage.setScene(scene); dialogStage.showAndWait(); - //When dialog closes update table view and disable edit and delete buttons, and reset search bar - displayProduct(); + displayProductWithFollowUp(); btnDelete.setDisable(true); btnEdit.setDisable(true); txtSearch.setText(""); -- 2.49.1 From 91e38fad45c39d98e7ec36002644c23567b7fa02 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 12:02:17 -0600 Subject: [PATCH 32/42] comment security and config --- .../backend/config/ActivityLoggingFilter.java | 23 ++++++++++++ ...tivityLoggingFilterRegistrationConfig.java | 6 +++ .../config/ApplicationStartupListener.java | 6 +++ .../backend/config/BusinessProperties.java | 7 ++++ .../petshop/backend/config/CacheConfig.java | 7 ++++ .../backend/config/DataInitializer.java | 8 ++++ .../config/FlywayContextInitializer.java | 7 ++++ .../config/LocalCatalogSeedInitializer.java | 7 ++++ .../config/TomcatPathToleranceConfig.java | 7 ++++ .../TrailingSlashNormalizationFilter.java | 14 +++++++ .../WebSocketAuthChannelInterceptor.java | 37 +++++++++++++++++++ .../backend/config/WebSocketConfig.java | 7 ++++ .../backend/security/AppPrincipal.java | 7 ++++ .../security/JwtAuthenticationFilter.java | 19 ++++++++++ .../com/petshop/backend/security/JwtUtil.java | 25 +++++++++++++ .../backend/security/RateLimitFilter.java | 7 ++++ .../backend/security/RateLimiterService.java | 17 +++++++++ .../security/RestAccessDeniedHandler.java | 7 ++++ .../RestAuthenticationEntryPoint.java | 6 +++ .../backend/security/SecurityConfig.java | 7 ++++ .../security/UserAuthCacheService.java | 7 ++++ .../security/UserDetailsServiceImpl.java | 6 +++ 22 files changed, 244 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java index 0c30cece..ef6749da 100644 --- a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java +++ b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilter.java @@ -1,3 +1,11 @@ +/* + * Logs write operations (POST, PUT, DELETE) to the activity log. + * Skips GET requests and internal endpoints like health checks. + * Builds a human-readable description for each action. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; @@ -77,11 +85,24 @@ public class ActivityLoggingFilter extends OncePerRequestFilter { activityLogService.record(userId, activity); } + /** + * Turns a request method + URL + status into a human-readable description + * like "Created a new product" or "Failed login attempt". + * Parses the URL as /api/v1/{resource}/{id?}/{sub?}/{subsub?} and matches + * against known patterns. + * @param method HTTP method (GET, POST, etc.) + * @param rawUri the request URI, possibly with query params + * @param status the HTTP response status code + * @return a readable description, or null if we don't recognize the pattern + */ private String describe(String method, String rawUri, int status) { + // Strip query params so we only deal with the path String uri = rawUri.contains("?") ? rawUri.substring(0, rawUri.indexOf('?')) : rawUri; + // Split into segments: [api, v1, resource, id/action, sub, ...] String[] parts = uri.replaceFirst("^/+", "").split("/"); if (parts.length < 3) return null; + // parts[2] is the resource name (e.g. "products", "auth", "cart") String r = parts[2]; String seg3 = parts.length > 3 ? parts[3] : null; String seg4 = parts.length > 4 ? parts[4] : null; @@ -89,6 +110,8 @@ public class ActivityLoggingFilter extends OncePerRequestFilter { boolean seg3IsId = seg3 != null && seg3.matches("\\d+"); boolean seg4IsId = seg4 != null && seg4.matches("\\d+"); + // Normalize: if seg3 is a numeric ID, treat seg4 as the sub-action + // otherwise seg3 itself is the sub-action (like "login", "add", etc.) String id = seg3IsId ? seg3 : null; String sub = seg3IsId ? seg4 : seg3; String subsub = seg3IsId ? seg5 : seg4; diff --git a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java index 66427661..b57f574d 100644 --- a/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/ActivityLoggingFilterRegistrationConfig.java @@ -1,3 +1,9 @@ +/* + * Registers the activity logging filter with the servlet container. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; diff --git a/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java b/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java index 24ad9a6a..f937ad82 100644 --- a/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java +++ b/backend/src/main/java/com/petshop/backend/config/ApplicationStartupListener.java @@ -1,3 +1,9 @@ +/* + * Runs once on startup to mark past appointments as completed. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.service.AppointmentService; diff --git a/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java index 6d1cee18..d154fe7e 100644 --- a/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java +++ b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java @@ -1,3 +1,10 @@ +/* + * Business settings loaded from application.yml (store hours, + * slot intervals, image size limits, discount/loyalty config). + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/backend/src/main/java/com/petshop/backend/config/CacheConfig.java b/backend/src/main/java/com/petshop/backend/config/CacheConfig.java index 4a8bc6b0..839002ca 100644 --- a/backend/src/main/java/com/petshop/backend/config/CacheConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/CacheConfig.java @@ -1,3 +1,10 @@ +/* + * Sets up Caffeine caching for the app. Currently used to cache + * user auth lookups so the filter doesn't query the DB every time. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.github.benmanes.caffeine.cache.Caffeine; diff --git a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java index 89cbb18e..56a26deb 100644 --- a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -1,3 +1,11 @@ +/* + * Creates default admin, staff, and customer accounts on startup + * if they don't already exist. Also fills in missing fields on + * existing accounts to keep them consistent. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java index 15c9b976..94d05d32 100644 --- a/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java @@ -1,3 +1,10 @@ +/* + * Runs Flyway migrations before the app starts. Retries up to + * 15 times so the app can wait for the database container to be ready. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.flywaydb.core.Flyway; diff --git a/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java b/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java index 18e64f05..74b6bfff 100644 --- a/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/LocalCatalogSeedInitializer.java @@ -1,3 +1,10 @@ +/* + * Adds extra pet and product seed data in the local dev profile + * if the database only has the base seed rows. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.repository.PetRepository; diff --git a/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java b/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java index 9a89c5ab..7b6b3d7e 100644 --- a/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java @@ -1,3 +1,10 @@ +/* + * Tells Tomcat to accept backslashes in URLs so the Android + * and desktop clients don't get rejected for path issues. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; diff --git a/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java b/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java index 38ececb9..f915ec4f 100644 --- a/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java +++ b/backend/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java @@ -1,3 +1,10 @@ +/* + * Strips trailing slashes and normalizes paths so /api/pets/ + * and /api/pets both hit the same controller. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import jakarta.servlet.FilterChain; @@ -63,6 +70,12 @@ public class TrailingSlashNormalizationFilter extends OncePerRequestFilter { filterChain.doFilter(wrapper, response); } + /** + * Cleans up a URL path by fixing backslashes, collapsing double slashes, + * lowercasing API/WS paths, and stripping trailing slashes. + * @param value the raw path string + * @return the normalized path, or null if input was null + */ private String normalizePath(String value) { if (value == null) { return null; @@ -74,6 +87,7 @@ public class TrailingSlashNormalizationFilter extends OncePerRequestFilter { if (shouldLowercase(normalized)) { normalized = normalized.toLowerCase(java.util.Locale.ROOT); } + // Strip trailing slashes but keep the root "/" intact int end = normalized.length(); while (end > 1 && normalized.charAt(end - 1) == '/') { end--; diff --git a/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java index c7f23fc4..0b55b216 100644 --- a/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java +++ b/backend/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -1,3 +1,11 @@ +/* + * Intercepts WebSocket messages to authenticate and authorize users. + * Validates the token on CONNECT, then checks permissions on + * SUBSCRIBE and SEND for chat topics. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import com.petshop.backend.entity.User; @@ -33,6 +41,14 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { this.chatService = chatService; } + /** + * Intercepts every STOMP message before it's sent. On CONNECT it validates the + * JWT and stores the authenticated user. On SUBSCRIBE/SEND it checks that the + * user has access to the requested chat destination. + * @param message the STOMP message being sent + * @param channel the channel the message is going through + * @return the original message if everything checks out + */ @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); @@ -95,6 +111,14 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return message; } + /** + * Tries to figure out who the user is from multiple sources: the principal on the + * message, the session attributes (fallback for some STOMP clients), or by + * re-parsing the token from headers as a last resort. + * @param principal the principal attached to the message, may be null + * @param accessor the STOMP header accessor for fallback lookups + * @return the User entity, or null if unauthenticated + */ private User resolveUser(Principal principal, StompHeaderAccessor accessor) { Principal currentPrincipal = principal; if (currentPrincipal == null && accessor.getSessionAttributes() != null) { @@ -136,6 +160,13 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return user; } + /** + * Checks if the user is allowed to subscribe to this destination. + * Customers can only subscribe to their own conversations, not the + * staff-wide conversation feed. + * @param destination the STOMP destination being subscribed to + * @param user the user trying to subscribe + */ private void authorizeSubscription(String destination, User user) { destination = normalizeDestination(destination); if (destination == null || destination.startsWith("/user/queue/")) { @@ -157,6 +188,12 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { throw new IllegalArgumentException("Not authorized to subscribe to destination"); } + /** + * Checks if the user is allowed to send a message to this destination. + * Only allows sending to conversation message endpoints that the user has access to. + * @param destination the STOMP destination being sent to + * @param user the user trying to send + */ private void authorizeSend(String destination, User user) { destination = normalizeDestination(destination); Long conversationId = extractConversationId(destination, "/app/chat/conversations/"); diff --git a/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java index 67dc1048..4ff58981 100644 --- a/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java +++ b/backend/src/main/java/com/petshop/backend/config/WebSocketConfig.java @@ -1,3 +1,10 @@ +/* + * WebSocket configuration for the live chat feature. + * Registers STOMP endpoints and the auth interceptor. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.config; import org.springframework.context.annotation.Configuration; diff --git a/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java b/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java index 30ceca66..f1a1c8b5 100644 --- a/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java +++ b/backend/src/main/java/com/petshop/backend/security/AppPrincipal.java @@ -1,3 +1,10 @@ +/* + * Represents the currently logged-in user in the security context. + * Holds the user ID, username, role, and token version. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index db7d47fc..52823e98 100644 --- a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -1,3 +1,10 @@ +/* + * Filter that runs on every request to validate the JWT token + * and set up the security context if the token is valid. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; @@ -30,12 +37,20 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { this.apiErrorResponder = apiErrorResponder; } + /** + * Extracts the JWT from the Authorization header, validates it, and sets up + * the Spring Security context so downstream filters/controllers know who the user is. + * @param request the incoming HTTP request + * @param response the HTTP response (used to write 401 errors) + * @param filterChain the remaining filters to invoke + */ @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { + // Skip requests that don't have a Bearer token final String authHeader = request.getHeader("Authorization"); final String jwt; if (authHeader == null || !authHeader.startsWith("Bearer ")) { @@ -43,6 +58,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Strip "Bearer " prefix to get the raw token jwt = authHeader.substring(7); Long userId; String username; @@ -58,6 +74,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Only set up auth context if there isn't one already for this request if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { if (jwtUtil.extractExpiration(jwt).before(new Date())) { writeUnauthorized(request, response, "Invalid or expired token", null); @@ -69,6 +86,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { writeUnauthorized(request, response, "User account is inactive", null); return; } + // Token version mismatch means the user logged out or changed password if (!authData.tokenVersion().equals(jwtTokenVersion)) { writeUnauthorized(request, response, "Invalid or expired token", null); return; @@ -82,6 +100,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } + // Build the authentication object and store it in the security context AppPrincipal principal = new AppPrincipal(userId, username, role, jwtTokenVersion); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( principal, diff --git a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java index b0a5fec5..fc4c0a4f 100644 --- a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java +++ b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java @@ -1,3 +1,10 @@ +/* + * JWT token utility for generating, parsing, and validating tokens. + * Tokens store userId, username, role, and a version for invalidation. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; @@ -50,6 +57,12 @@ public class JwtUtil { return extractAllClaims(token).get("role", String.class); } + /** + * Gets the token version from the JWT, used to invalidate old tokens + * when the user logs out or changes their password. + * @param token the JWT string + * @return the token version number, or null if not present + */ public Integer extractTokenVersion(String token) { Number tokenVersion = extractAllClaims(token).get("tokenVersion", Number.class); return tokenVersion == null ? null : tokenVersion.intValue(); @@ -76,6 +89,11 @@ public class JwtUtil { return extractExpiration(token).before(new Date()); } + /** + * Creates a new JWT containing the user's id, username, role, and token version. + * @param user the user to generate a token for + * @return the signed JWT string + */ public String generateToken(User user) { Map claims = new HashMap<>(); claims.put("username", user.getUsername()); @@ -94,6 +112,13 @@ public class JwtUtil { .compact(); } + /** + * Checks that the token belongs to this user, has the right role and version, + * and hasn't expired yet. + * @param token the JWT string + * @param user the user to validate against + * @return true if the token is valid for this user + */ public Boolean validateToken(String token, User user) { Long userId = extractUserId(token); String role = extractRole(token); diff --git a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java index 31f2434f..fd060f1c 100644 --- a/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java +++ b/backend/src/main/java/com/petshop/backend/security/RateLimitFilter.java @@ -1,3 +1,10 @@ +/* + * Filter that applies rate limiting to auth endpoints. + * Each rule defines max requests and window size in minutes. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java b/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java index 4f6eb94f..a95bafd0 100644 --- a/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java +++ b/backend/src/main/java/com/petshop/backend/security/RateLimiterService.java @@ -1,3 +1,10 @@ +/* + * Sliding-window rate limiter that tracks request timestamps per key. + * Used to limit login/register attempts by IP. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import org.springframework.scheduling.annotation.Scheduled; @@ -15,12 +22,22 @@ public class RateLimiterService { private final Map> buckets = new ConcurrentHashMap<>(); + /** + * Checks whether a request identified by key is within the rate limit. + * Uses a sliding window: keeps a queue of timestamps per key, drops any + * that fall outside the window, then checks if there's room for one more. + * @param key identifier for the rate limit bucket (e.g. IP address) + * @param maxRequests max number of requests allowed in the window + * @param window the time window to count requests in + * @return true if the request is allowed, false if rate limited + */ public boolean isAllowed(String key, int maxRequests, Duration window) { Instant now = Instant.now(); Instant windowStart = now.minus(window); Deque timestamps = buckets.computeIfAbsent(key, k -> new ArrayDeque<>()); synchronized (timestamps) { + // Remove timestamps that are older than the window while (!timestamps.isEmpty() && timestamps.peekFirst().isBefore(windowStart)) { timestamps.pollFirst(); } diff --git a/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java b/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java index fcaa49bc..dc41867b 100644 --- a/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java +++ b/backend/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java @@ -1,3 +1,10 @@ +/* + * Returns a JSON error when a logged-in user tries to access + * something they don't have permission for. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java b/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java index fb28314b..ba95590f 100644 --- a/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java +++ b/backend/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java @@ -1,3 +1,9 @@ +/* + * Returns a JSON error when an unauthenticated request hits a protected endpoint. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.exception.ApiErrorResponder; diff --git a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java index f154ead1..4d83867c 100644 --- a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -1,3 +1,10 @@ +/* + * Security setup for the app. Defines which endpoints are public, + * which need login, and configures the filter chain. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java b/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java index 69d39a9d..c67d7da9 100644 --- a/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java +++ b/backend/src/main/java/com/petshop/backend/security/UserAuthCacheService.java @@ -1,3 +1,10 @@ +/* + * Caches user auth data (active status, token version) so the + * JWT filter doesn't hit the database on every request. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.repository.UserRepository; diff --git a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index f6956615..87b6463d 100644 --- a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -1,3 +1,9 @@ +/* + * Loads user details from the database for Spring Security login. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.security; import com.petshop.backend.entity.User; -- 2.49.1 From 539e084f9bf73a1e4ef08ecfee8bf5c808a433b2 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 12:38:42 -0600 Subject: [PATCH 33/42] add service file headers --- .../com/petshop/backend/service/ActivityLogService.java | 7 +++++++ .../java/com/petshop/backend/service/AnalyticsService.java | 7 +++++++ .../com/petshop/backend/service/AvatarStorageService.java | 7 +++++++ .../java/com/petshop/backend/service/AzureBlobService.java | 7 +++++++ .../backend/service/CatalogImageStorageService.java | 7 +++++++ .../java/com/petshop/backend/service/CategoryService.java | 6 ++++++ .../backend/service/ChatAttachmentStorageService.java | 7 +++++++ .../java/com/petshop/backend/service/CouponService.java | 7 +++++++ .../java/com/petshop/backend/service/InventoryService.java | 7 +++++++ .../com/petshop/backend/service/PasswordResetService.java | 7 +++++++ .../java/com/petshop/backend/service/ProductService.java | 7 +++++++ .../petshop/backend/service/ProductSupplierService.java | 7 +++++++ .../com/petshop/backend/service/PurchaseOrderService.java | 7 +++++++ .../java/com/petshop/backend/service/RefundService.java | 7 +++++++ .../java/com/petshop/backend/service/ServiceService.java | 7 +++++++ .../java/com/petshop/backend/service/StoreService.java | 6 ++++++ .../java/com/petshop/backend/service/SupplierService.java | 6 ++++++ .../main/java/com/petshop/backend/service/UserService.java | 7 +++++++ 18 files changed, 123 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java b/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java index 598d9f95..fd1efdda 100644 --- a/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java +++ b/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java @@ -1,3 +1,10 @@ +/* + * Records and retrieves user activity logs so admins can + * see who did what and when. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.activity.ActivityLogResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java index eae169d0..c8772274 100644 --- a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -1,3 +1,10 @@ +/* + * Builds the dashboard data by pulling together sales totals, + * inventory levels, and top-selling products. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.analytics.DashboardResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java index 5ff61021..e700e562 100644 --- a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java @@ -1,3 +1,10 @@ +/* + * Stores and retrieves user profile avatar images, using + * either local disk or Azure Blob Storage. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/service/AzureBlobService.java b/backend/src/main/java/com/petshop/backend/service/AzureBlobService.java index cbdfc62d..a8a7d6dd 100644 --- a/backend/src/main/java/com/petshop/backend/service/AzureBlobService.java +++ b/backend/src/main/java/com/petshop/backend/service/AzureBlobService.java @@ -1,3 +1,10 @@ +/* + * Low-level wrapper around the Azure Blob Storage SDK for + * uploading, downloading, and deleting files. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.azure.storage.blob.BlobContainerClient; diff --git a/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java b/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java index 229ac5ba..69e28837 100644 --- a/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java @@ -1,3 +1,10 @@ +/* + * Stores and retrieves images for pets and products, using + * either local disk or Azure Blob Storage. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import org.slf4j.Logger; diff --git a/backend/src/main/java/com/petshop/backend/service/CategoryService.java b/backend/src/main/java/com/petshop/backend/service/CategoryService.java index ea08dc3d..327d7ba7 100644 --- a/backend/src/main/java/com/petshop/backend/service/CategoryService.java +++ b/backend/src/main/java/com/petshop/backend/service/CategoryService.java @@ -1,3 +1,9 @@ +/* + * Handles creating, updating, and deleting product categories. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.category.CategoryRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/ChatAttachmentStorageService.java b/backend/src/main/java/com/petshop/backend/service/ChatAttachmentStorageService.java index 55932b92..20d94e88 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatAttachmentStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatAttachmentStorageService.java @@ -1,3 +1,10 @@ +/* + * Stores and retrieves file attachments sent in chat messages, + * using either local disk or Azure Blob Storage. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import org.springframework.beans.factory.annotation.Autowired; diff --git a/backend/src/main/java/com/petshop/backend/service/CouponService.java b/backend/src/main/java/com/petshop/backend/service/CouponService.java index cf56188f..87d28c6c 100644 --- a/backend/src/main/java/com/petshop/backend/service/CouponService.java +++ b/backend/src/main/java/com/petshop/backend/service/CouponService.java @@ -1,3 +1,10 @@ +/* + * Handles creating, updating, and validating discount coupons + * that can be applied to sales. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/InventoryService.java b/backend/src/main/java/com/petshop/backend/service/InventoryService.java index af133e68..c71dcc15 100644 --- a/backend/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/backend/src/main/java/com/petshop/backend/service/InventoryService.java @@ -1,3 +1,10 @@ +/* + * Manages inventory records that track how much of each + * product is available at each store location. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java b/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java index 6c5c78f5..1080bc35 100644 --- a/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java @@ -1,3 +1,10 @@ +/* + * Handles the forgot-password flow by generating a reset token, + * emailing it to the user, and verifying it when they reset. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.auth.ForgotPasswordResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/ProductService.java b/backend/src/main/java/com/petshop/backend/service/ProductService.java index ad4524fe..b9061d0c 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductService.java +++ b/backend/src/main/java/com/petshop/backend/service/ProductService.java @@ -1,3 +1,10 @@ +/* + * Manages products in the catalog, including searching, + * creating, updating, and handling product images. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java index 5adc79fb..25545918 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java +++ b/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -1,3 +1,10 @@ +/* + * Manages the links between products and their suppliers, + * including the cost each supplier charges for a product. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.productsupplier.BulkDeleteProductSupplierRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index a6803d37..eaa712c2 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -1,3 +1,10 @@ +/* + * Provides read-only access to purchase orders, which track + * orders placed with suppliers for restocking stores. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/RefundService.java b/backend/src/main/java/com/petshop/backend/service/RefundService.java index 6cfff23d..0716bd90 100644 --- a/backend/src/main/java/com/petshop/backend/service/RefundService.java +++ b/backend/src/main/java/com/petshop/backend/service/RefundService.java @@ -1,3 +1,10 @@ +/* + * Processes refunds for sales, calculating the refund amount + * and updating the original sale record. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.refund.RefundItemResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/ServiceService.java b/backend/src/main/java/com/petshop/backend/service/ServiceService.java index ecf644c2..f05b694f 100644 --- a/backend/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/backend/src/main/java/com/petshop/backend/service/ServiceService.java @@ -1,3 +1,10 @@ +/* + * Manages pet services like grooming and veterinary care, + * including searching and filtering by species. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/StoreService.java b/backend/src/main/java/com/petshop/backend/service/StoreService.java index d9c1bcd3..2dfb763e 100644 --- a/backend/src/main/java/com/petshop/backend/service/StoreService.java +++ b/backend/src/main/java/com/petshop/backend/service/StoreService.java @@ -1,3 +1,9 @@ +/* + * Handles creating, updating, and deleting store locations. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/SupplierService.java b/backend/src/main/java/com/petshop/backend/service/SupplierService.java index 4bb79b5a..a1bb3955 100644 --- a/backend/src/main/java/com/petshop/backend/service/SupplierService.java +++ b/backend/src/main/java/com/petshop/backend/service/SupplierService.java @@ -1,3 +1,9 @@ +/* + * Handles creating, updating, and deleting suppliers. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/service/UserService.java b/backend/src/main/java/com/petshop/backend/service/UserService.java index 99e25747..587ce9d6 100644 --- a/backend/src/main/java/com/petshop/backend/service/UserService.java +++ b/backend/src/main/java/com/petshop/backend/service/UserService.java @@ -1,3 +1,10 @@ +/* + * Handles creating, updating, and deleting user accounts + * and managing their roles and store assignments. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; -- 2.49.1 From 7f1e237406ac546354927d6d0694f41622a154da Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 13:03:21 -0600 Subject: [PATCH 34/42] add service file headers --- .../backend/service/AdoptionService.java | 29 ++++++++++++++++ .../backend/service/AppointmentService.java | 28 ++++++++++++++- .../petshop/backend/service/CartService.java | 34 +++++++++++++++++++ .../backend/service/ChatRealtimeService.java | 7 ++++ .../petshop/backend/service/ChatService.java | 17 ++++++++++ .../petshop/backend/service/EmailService.java | 7 ++++ .../backend/service/OpenRouterAiService.java | 16 +++++++++ .../backend/service/OpenRouterService.java | 7 ++++ .../petshop/backend/service/PetService.java | 24 +++++++++++++ .../petshop/backend/service/SaleService.java | 27 +++++++++++++++ 10 files changed, 195 insertions(+), 1 deletion(-) 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 b99be2e2..a5b122e8 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -1,3 +1,10 @@ +/* + * Handles the pet adoption workflow from application + * through to approval and completion. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.adoption.AdoptionRequest; @@ -165,6 +172,10 @@ public class AdoptionService { return mapToResponse(adoption); } + /** + * Customer-facing adoption request. Validates the pet is at the claimed store + * and available, then creates a Pending adoption and marks the pet as Pending. + */ @Transactional public AdoptionResponse requestAdoption(Long customerId, Long petId, Long employeeId, Long sourceStoreId, LocalDate adoptionDate) { Pet pet = petRepository.findByIdForUpdate(petId) @@ -235,6 +246,10 @@ public class AdoptionService { } } + /** + * When a Pending adoption is deleted, resets the pet back to Available + * unless another completed adoption already exists for it. + */ private void resetPetIfPending(Pet pet, String deletedAdoptionStatus) { if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(deletedAdoptionStatus)) { return; @@ -309,6 +324,11 @@ public class AdoptionService { throw new BusinessException("Adoption status must be Pending, Completed, Cancelled, or Missed"); } + /** + * Ensures a pet hasn't already been adopted and is in Available status. + * When updating an existing adoption, skips the status check if the pet + * is the same one already attached to this adoption record. + */ private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) { boolean samePetAsCurrentAdoption = currentPetId != null && currentPetId.equals(pet.getPetId()); boolean adoptedElsewhere = adoptionId == null @@ -323,6 +343,10 @@ public class AdoptionService { } } + /** + * Creates a sale record when an adoption is marked Completed, using + * the pet's price as the sale amount. Skipped if no employee or store is set. + */ private void createSaleForAdoption(Adoption adoption, String paymentMethod) { if (adoption.getEmployee() == null || adoption.getSourceStore() == null) { return; @@ -345,6 +369,11 @@ public class AdoptionService { eventPublisher.publishEvent(new SaleReceiptEvent(savedSale.getSaleId())); } + /** + * Keeps the pet's status in sync with the adoption lifecycle. + * Completed -> Adopted (removed from store), Pending -> Pending (reserved), + * Cancelled/Missed -> Available (back on the floor). + */ private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) { boolean completedElsewhere = adoptionId != null && adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId); diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index 048bdc37..5ad5d46e 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -1,3 +1,10 @@ +/* + * Manages appointment booking, availability checking, and + * scheduled status updates for grooming and vet visits. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.appointment.AppointmentRequest; @@ -222,6 +229,15 @@ public class AppointmentService { appointmentRepository.deleteAllById(request.getIds()); } + /** + * Returns all bookable time slots for a given store, service, and date. + * Iterates through the business hours in fixed intervals, checking each + * slot against every employee at the store to see if at least one is free. + * @param storeId store to check + * @param serviceId service being booked (determines slot duration) + * @param date the date to check availability for + * @return list of available start times as strings + */ @Transactional(readOnly = true) public List checkAvailability(Long storeId, Long serviceId, LocalDate date) { storeRepository.findById(storeId) @@ -236,6 +252,7 @@ public class AppointmentService { return List.of(); } + // Batch-load all appointments for the day to avoid N+1 queries per employee List employeeIds = assignableUsers.stream().map(User::getId).collect(Collectors.toList()); List allAppointments = appointmentRepository.findByEmployeeIdInAndAppointmentDate(employeeIds, date); @@ -245,6 +262,7 @@ public class AppointmentService { List availableSlots = new ArrayList<>(); LocalTime startTime = businessProperties.openTime(); LocalTime endTime = businessProperties.closeTime(); + // Last slot must leave enough time for the service to finish before closing LocalTime latestStart = endTime.minusMinutes(service.getServiceDuration()); LocalTime currentTime = startTime; @@ -264,7 +282,11 @@ public class AppointmentService { return availableSlots; } - //Update booked status to completed at every midnight, and send 24h reminders + /** + * Midnight cron job that marks past booked appointments as Completed, + * sends 24-hour reminder emails for tomorrow's appointments, + * and sends reminders for pending adoptions scheduled for tomorrow. + */ @Scheduled(cron = "0 0 0 * * ?") @Transactional public void updatePastAppointmentsStatus() { @@ -379,6 +401,10 @@ public class AppointmentService { } } + /** + * Checks if a time slot is open by looking for overlaps with existing appointments. + * Uses interval overlap detection: two ranges overlap when each starts before the other ends. + */ private boolean isSlotAvailable(List existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) { LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration()); for (Appointment existingAppointment : existingAppointments) { diff --git a/backend/src/main/java/com/petshop/backend/service/CartService.java b/backend/src/main/java/com/petshop/backend/service/CartService.java index 305d5ef4..77e76f1a 100644 --- a/backend/src/main/java/com/petshop/backend/service/CartService.java +++ b/backend/src/main/java/com/petshop/backend/service/CartService.java @@ -1,3 +1,10 @@ +/* + * Handles shopping cart operations, checkout with Stripe payments, + * and loyalty points redemption. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.cart.*; @@ -230,6 +237,14 @@ public class CartService { return toResponse(cart); } + /** + * Starts the checkout flow for a user's active cart at a given store. + * Handles free checkouts (points cover everything or total is $0) directly, + * otherwise creates a Stripe PaymentIntent for card payment. + * @param userId the customer checking out + * @param storeId the store the cart belongs to + * @return checkout response with client secret for Stripe or success status for free orders + */ @Transactional public CheckoutResponse checkout(Long userId, Long storeId) { Cart cart = cartRepository @@ -316,6 +331,13 @@ public class CartService { } } + /** + * Finalizes checkout after Stripe confirms payment succeeded. + * Validates the payment intent metadata, re-checks the cart total hasn't + * drifted since checkout started, and creates the sale record. + * @param userId the customer completing checkout + * @param paymentIntentId Stripe payment intent to verify + */ @Transactional public void completeCheckout(Long userId, String paymentIntentId) { try { @@ -376,11 +398,13 @@ public class CartService { throw new BusinessException("Cart items were removed during checkout"); } + // Recalculate to make sure prices/coupons haven't changed mid-checkout BigDecimal recalculatedTotal = recalculateTotalAmount(cart); if (recalculatedTotal.compareTo(cart.getCheckoutAmount()) != 0) { throw new BusinessException("Cart total changed during checkout"); } + // Cross-check that what Stripe actually charged matches our expected amount long storedAmountInCents = cart.getCheckoutAmount() .multiply(BigDecimal.valueOf(100)) .setScale(0, RoundingMode.HALF_UP) @@ -390,6 +414,7 @@ public class CartService { throw new BusinessException("Stripe charged amount does not match expected amount"); } + // Guard against duplicate sale creation if completeCheckout is called twice if (saleRepository.findByCartCartId(cart.getCartId()).isPresent()) { cart.setCartStatus("CHECKED_OUT"); cart.setCheckoutPending(false); @@ -438,6 +463,10 @@ public class CartService { private record CartTotals(BigDecimal subtotal, BigDecimal discount, BigDecimal pointsDiscount, BigDecimal total) {} + /** + * Computes subtotal, coupon discount, points discount, and final total for a cart. + * Discounts are applied in order: coupon first, then loyalty points on the remainder. + */ private CartTotals computeTotals(Cart cart) { List items = cartItemRepository.findByCartCartId(cart.getCartId()); @@ -468,6 +497,11 @@ public class CartService { return computeTotals(cart).total(); } + /** + * Converts the user's loyalty points into a dollar discount. + * Points are converted at the configured rate (e.g. 100 points = $1). + * The discount is capped at the remaining amount so the total never goes negative. + */ private BigDecimal calculatePointsDiscount(User user, BigDecimal remainingAmount, boolean pointsApplied) { if (!pointsApplied || user == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; diff --git a/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java b/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java index d6b9d380..4e9f1c63 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java @@ -1,3 +1,10 @@ +/* + * Sends real-time chat updates to connected clients + * through WebSocket messages. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.chat.ConversationResponse; diff --git a/backend/src/main/java/com/petshop/backend/service/ChatService.java b/backend/src/main/java/com/petshop/backend/service/ChatService.java index bf519f23..77b866d7 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -1,3 +1,10 @@ +/* + * Manages chat conversations and messages between + * customers and store staff. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.chat.ConversationRequest; @@ -76,6 +83,11 @@ public class ChatService { return ConversationResponse.fromEntity(conversation, greeting, botUser.getId()); } + /** + * Lists conversations visible to the user based on their role. + * Customers only see their own. Staff see conversations assigned to them + * plus unassigned ones. Admins see everything. + */ public List getConversations(Long userId, User.Role role, boolean mine) { List conversations; @@ -165,6 +177,7 @@ public class ChatService { message.setIsRead(false); message = messageRepository.save(message); + // When staff replies, claim the conversation and switch from AI to human mode if (role == User.Role.STAFF && conversation.getStaffId() == null) { conversation.setStaffId(userId); } @@ -376,6 +389,10 @@ public class ChatService { return hasConversationAccess(conversation, userId, role); } + /** + * Access rules: admins can see all conversations, customers only their own, + * and staff can see unassigned conversations or ones assigned to them. + */ private boolean hasConversationAccess(Conversation conversation, Long userId, User.Role role) { if (role == User.Role.ADMIN) { return true; diff --git a/backend/src/main/java/com/petshop/backend/service/EmailService.java b/backend/src/main/java/com/petshop/backend/service/EmailService.java index 6e096285..043c3cb9 100644 --- a/backend/src/main/java/com/petshop/backend/service/EmailService.java +++ b/backend/src/main/java/com/petshop/backend/service/EmailService.java @@ -1,3 +1,10 @@ +/* + * Sends transactional emails like receipts and confirmations + * using the Resend API. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.entity.Adoption; diff --git a/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java b/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java index d2e007dc..84f699e6 100644 --- a/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java +++ b/backend/src/main/java/com/petshop/backend/service/OpenRouterAiService.java @@ -1,3 +1,10 @@ +/* + * Manages the AI chatbot conversation flow, building context + * and coordinating responses from OpenRouter. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.fasterxml.jackson.databind.JsonNode; @@ -67,6 +74,12 @@ public class OpenRouterAiService { CompletableFuture.runAsync(() -> generateReply(conversationId, triggerMessageId)); } + /** + * Generates an AI reply for a conversation by calling the OpenRouter API. + * Checks conversation state at multiple points to avoid replying if the + * conversation was closed, handed off to a human, or a newer message arrived + * while the API call was in flight. + */ private void generateReply(Long conversationId, Long triggerMessageId) { try { if (triggerMessageId == null) { @@ -105,6 +118,7 @@ public class OpenRouterAiService { return; } + // Only reply if the trigger is still the latest message (no newer messages came in) Message lastMessage = history.get(history.size() - 1); if (!triggerMessageId.equals(lastMessage.getId())) { return; @@ -167,6 +181,7 @@ public class OpenRouterAiService { return; } + // Re-check conversation state after the API call in case anything changed while waiting Conversation latestConversation = chatService.getConversationEntity(conversationId); if (latestConversation.getStatus() == Conversation.ConversationStatus.CLOSED || latestConversation.getHumanRequestedAt() != null @@ -174,6 +189,7 @@ public class OpenRouterAiService { return; } + // Also re-check that no new messages were sent while the API call was in progress List latestHistory = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); if (latestHistory.isEmpty() || !triggerMessageId.equals(latestHistory.get(latestHistory.size() - 1).getId())) { return; diff --git a/backend/src/main/java/com/petshop/backend/service/OpenRouterService.java b/backend/src/main/java/com/petshop/backend/service/OpenRouterService.java index c3314994..60e416ac 100644 --- a/backend/src/main/java/com/petshop/backend/service/OpenRouterService.java +++ b/backend/src/main/java/com/petshop/backend/service/OpenRouterService.java @@ -1,3 +1,10 @@ +/* + * Sends chat messages to the OpenRouter API and parses + * the AI model response. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.fasterxml.jackson.databind.JsonNode; diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/backend/src/main/java/com/petshop/backend/service/PetService.java index 1c4cf875..552fdb40 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -1,3 +1,10 @@ +/* + * Handles creating, updating, and listing pets with + * visibility rules based on user role. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; @@ -53,6 +60,11 @@ public class PetService { this.catalogImageStorageService = catalogImageStorageService; } + /** + * Lists pets with role-based visibility filtering. + * Unauthenticated users only see Available pets. Customers see Available + * pets plus their own Adopted/Owned ones. Staff and admins see all. + */ @Transactional(readOnly = true) public Page getAllPets(String query, String species, String breed, String status, Long storeId, Long customerId, Pageable pageable) { String normalizedQuery = StringUtils.trimToNull(query); @@ -229,6 +241,10 @@ public class PetService { return "available".equalsIgnoreCase(normalizeStatus(pet.getPetStatus())); } + /** + * Visibility rules: Available pets are public. Staff/admins see everything. + * Customers can see pets they own or have adopted. + */ private boolean canViewPet(Pet pet, CurrentViewer viewer) { if (isPubliclyVisible(pet)) { return true; @@ -245,6 +261,9 @@ public class PetService { return isAdoptedByUser(pet, viewer.userId()); } + /** + * Checks if the pet was adopted by this user by looking for a Completed adoption record. + */ private boolean isAdoptedByUser(Pet pet, Long userId) { if (userId == null) { return false; @@ -354,6 +373,11 @@ public class PetService { return trimmed.isEmpty() ? null : trimmed; } + /** + * Sets the owner and store based on pet status. Owned pets get an owner + * but no store. Available pets get a store but no owner. Other statuses + * can have both. + */ private void applyOwnerAndStore(Pet pet, PetRequest request) { if ("owned".equalsIgnoreCase(request.getPetStatus())) { if (request.getCustomerId() != null) { diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index d33d11b3..c21d2143 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -1,3 +1,10 @@ +/* + * Creates and manages sales records, including refunds and + * discount calculations. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.service; import com.petshop.backend.dto.sale.SaleRequest; @@ -62,6 +69,14 @@ public class SaleService { return mapToResponse(sale); } + /** + * Creates a sale or refund record. For regular sales, deducts inventory and + * applies coupon/employee/loyalty discounts in that order. For refunds, + * restores inventory and proportionally reverses discounts and loyalty points + * based on the refund-to-original subtotal ratio. + * @param request sale details including items, store, and discount info + * @return the created sale + */ @Transactional public SaleResponse createSale(SaleRequest request) { User actor = AuthenticationHelper.getAuthenticatedUser(userRepository); @@ -136,6 +151,7 @@ public class SaleService { " for product: " + product.getProdName()); } + // Sum up quantities already refunded across all prior refunds for this product int alreadyRefundedQuantity = saleRepository.findByOriginalSaleSaleId(sale.getOriginalSale().getSaleId()).stream() .flatMap(existingRefund -> existingRefund.getItems().stream()) .filter(existingRefundItem -> existingRefundItem.getProduct().getProdId().equals(itemRequest.getProdId())) @@ -176,6 +192,7 @@ public class SaleService { BigDecimal couponDiscountRefunded = BigDecimal.ZERO; BigDecimal refundTotal; + // Proportionally split discounts based on what fraction of the original order is being refunded if (originalSubtotal != null && originalSubtotal.compareTo(BigDecimal.ZERO) > 0) { BigDecimal ratio = subtotalAmount.abs().divide(originalSubtotal, 10, RoundingMode.HALF_UP); refundTotal = originalSale.getTotalAmount().abs().multiply(ratio).negate().setScale(2, RoundingMode.HALF_UP); @@ -188,6 +205,7 @@ public class SaleService { User refundCustomer = originalSale.getCustomer(); if (refundCustomer != null) { sale.setCustomer(refundCustomer); + // Give back the points that were spent, subtract the points that were earned int pointsToRestore = toPointsUsed(loyaltyDiscountRefunded); int pointsEarnedToReverse = originalSale.getPointsEarned() != null ? ratio.multiply(BigDecimal.valueOf(originalSale.getPointsEarned())).setScale(0, RoundingMode.FLOOR).intValue() @@ -245,6 +263,7 @@ public class SaleService { BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount)); sale.setEmployeeDiscountAmount(employeeDiscount); + // Loyalty discount is applied last, after coupon and employee discounts BigDecimal remainingAfterDiscounts = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount); BigDecimal loyaltyDiscount; int pointsDeducted; @@ -265,6 +284,7 @@ public class SaleService { BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount); sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO)); + // Earn 1 point per dollar spent (floor), then update the customer's balance sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue()); if (customer != null) { int currentPoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0; @@ -314,6 +334,9 @@ public class SaleService { .setScale(2, RoundingMode.HALF_UP); } + /** + * Converts a dollar discount back into the number of loyalty points it represents. + */ private int toPointsUsed(BigDecimal loyaltyDiscount) { if (loyaltyDiscount == null || loyaltyDiscount.compareTo(BigDecimal.ZERO) <= 0) { return 0; @@ -321,6 +344,10 @@ public class SaleService { return loyaltyDiscount.setScale(0, RoundingMode.DOWN).intValue() * businessProperties.loyaltyPointsPerDollar(); } + /** + * Website sales need an employee on record. Picks a staff member at the + * store, or falls back to any admin if no staff is available. + */ private User resolveWebsiteSaleEmployee(Long storeId) { return userRepository.findFirstByPrimaryStoreStoreIdAndRoleAndActiveTrueOrderByIdAsc(storeId, User.Role.STAFF) .or(() -> userRepository.findFirstByRoleAndActiveTrueOrderByIdAsc(User.Role.ADMIN)) -- 2.49.1 From 1b865c79f6ba25e02788052ae89360140406a644 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 13:47:08 -0600 Subject: [PATCH 35/42] comment backend controllers --- .../petshop/backend/controller/ActivityLogController.java | 6 ++++++ .../com/petshop/backend/controller/AdoptionController.java | 6 ++++++ .../com/petshop/backend/controller/AiChatController.java | 6 ++++++ .../com/petshop/backend/controller/AnalyticsController.java | 6 ++++++ .../petshop/backend/controller/AppointmentController.java | 6 ++++++ .../java/com/petshop/backend/controller/AuthController.java | 6 ++++++ .../java/com/petshop/backend/controller/CartController.java | 6 ++++++ .../com/petshop/backend/controller/CategoryController.java | 6 ++++++ .../java/com/petshop/backend/controller/ChatController.java | 6 ++++++ .../petshop/backend/controller/ChatWebSocketController.java | 6 ++++++ .../com/petshop/backend/controller/ContactController.java | 6 ++++++ .../com/petshop/backend/controller/CouponController.java | 6 ++++++ .../com/petshop/backend/controller/CustomerController.java | 6 ++++++ .../com/petshop/backend/controller/DropdownController.java | 6 ++++++ .../com/petshop/backend/controller/EmployeeController.java | 6 ++++++ .../com/petshop/backend/controller/HealthController.java | 6 ++++++ .../com/petshop/backend/controller/InventoryController.java | 6 ++++++ .../com/petshop/backend/controller/MyPetController.java | 6 ++++++ .../java/com/petshop/backend/controller/PetController.java | 6 ++++++ .../com/petshop/backend/controller/PetImageController.java | 6 ++++++ .../com/petshop/backend/controller/ProductController.java | 6 ++++++ .../petshop/backend/controller/ProductImageController.java | 6 ++++++ .../backend/controller/ProductSupplierController.java | 6 ++++++ .../petshop/backend/controller/PurchaseOrderController.java | 6 ++++++ .../com/petshop/backend/controller/RefundController.java | 6 ++++++ .../java/com/petshop/backend/controller/SaleController.java | 6 ++++++ .../com/petshop/backend/controller/ServiceController.java | 6 ++++++ .../com/petshop/backend/controller/StoreController.java | 6 ++++++ .../com/petshop/backend/controller/SupplierController.java | 6 ++++++ .../petshop/backend/controller/UserAvatarController.java | 6 ++++++ .../java/com/petshop/backend/controller/UserController.java | 6 ++++++ 31 files changed, 186 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java b/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java index 6d202ddb..35e9f79c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java @@ -1,3 +1,9 @@ +/* + * Handles requests for viewing user activity logs. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.activity.ActivityLogResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java index 1be65273..2fa0fc71 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -1,3 +1,9 @@ +/* + * Handles pet adoption requests and approvals. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.adoption.AdoptionRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/AiChatController.java b/backend/src/main/java/com/petshop/backend/controller/AiChatController.java index a20ef4f7..733cafb4 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AiChatController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AiChatController.java @@ -1,3 +1,9 @@ +/* + * Handles AI-powered chat for getting pet care advice. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.ai.AiChatRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java b/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java index 08aea44d..bd7cde45 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java @@ -1,3 +1,9 @@ +/* + * Handles dashboard analytics and sales reporting for staff and admins. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.analytics.DashboardResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java b/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java index 301b4a77..7ce43cd6 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -1,3 +1,9 @@ +/* + * Handles booking and managing pet service appointments. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.appointment.AppointmentRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index e1d14e67..ecbaea8d 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -1,3 +1,9 @@ +/* + * Handles user login, registration, password reset, and profile updates. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.auth.AvatarUploadResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/CartController.java b/backend/src/main/java/com/petshop/backend/controller/CartController.java index 975d786a..bb535b09 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CartController.java +++ b/backend/src/main/java/com/petshop/backend/controller/CartController.java @@ -1,3 +1,9 @@ +/* + * Handles shopping cart operations like adding and removing items. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.cart.*; diff --git a/backend/src/main/java/com/petshop/backend/controller/CategoryController.java b/backend/src/main/java/com/petshop/backend/controller/CategoryController.java index bbce6ec9..da6f3a9f 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/backend/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -1,3 +1,9 @@ +/* + * Handles managing product and pet categories. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.category.CategoryRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/ChatController.java b/backend/src/main/java/com/petshop/backend/controller/ChatController.java index f1e26209..29fef9c1 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ChatController.java @@ -1,3 +1,9 @@ +/* + * Handles chat conversations and messages between users. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.chat.ConversationRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java b/backend/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java index 4bee487c..84274143 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java @@ -1,3 +1,9 @@ +/* + * Handles real-time chat messaging over WebSocket connections. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.config.WebSocketAuthChannelInterceptor; diff --git a/backend/src/main/java/com/petshop/backend/controller/ContactController.java b/backend/src/main/java/com/petshop/backend/controller/ContactController.java index cfd4326f..f91fa983 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ContactController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ContactController.java @@ -1,3 +1,9 @@ +/* + * Handles sending contact form messages from customers. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/controller/CouponController.java b/backend/src/main/java/com/petshop/backend/controller/CouponController.java index 44fdbd04..a9ab098c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CouponController.java +++ b/backend/src/main/java/com/petshop/backend/controller/CouponController.java @@ -1,3 +1,9 @@ +/* + * Handles creating and managing discount coupons. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java b/backend/src/main/java/com/petshop/backend/controller/CustomerController.java index 534fc06e..4ed81357 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java +++ b/backend/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -1,3 +1,9 @@ +/* + * Handles staff and admin operations for managing customer accounts. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; 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 756a9fca..0c7fe2ae 100644 --- a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -1,3 +1,9 @@ +/* + * Handles providing dropdown menu options for forms across the app. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.DropdownOption; diff --git a/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java b/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java index 05cc8553..41a4b50d 100644 --- a/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java +++ b/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java @@ -1,3 +1,9 @@ +/* + * Handles admin operations for managing employee accounts. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.user.UserRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/HealthController.java b/backend/src/main/java/com/petshop/backend/controller/HealthController.java index 8ee609c3..ce6729b9 100644 --- a/backend/src/main/java/com/petshop/backend/controller/HealthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/HealthController.java @@ -1,3 +1,9 @@ +/* + * Handles health check requests to verify the server is running. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import org.springframework.http.ResponseEntity; diff --git a/backend/src/main/java/com/petshop/backend/controller/InventoryController.java b/backend/src/main/java/com/petshop/backend/controller/InventoryController.java index f7aea48f..f6716e1d 100644 --- a/backend/src/main/java/com/petshop/backend/controller/InventoryController.java +++ b/backend/src/main/java/com/petshop/backend/controller/InventoryController.java @@ -1,3 +1,9 @@ +/* + * Handles tracking product stock levels across store locations. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/MyPetController.java b/backend/src/main/java/com/petshop/backend/controller/MyPetController.java index 5d4ad1ef..e9f12262 100644 --- a/backend/src/main/java/com/petshop/backend/controller/MyPetController.java +++ b/backend/src/main/java/com/petshop/backend/controller/MyPetController.java @@ -1,3 +1,9 @@ +/* + * Handles customers viewing and managing their own pets. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.pet.MyPetRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/PetController.java b/backend/src/main/java/com/petshop/backend/controller/PetController.java index a708a4db..d83841ae 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PetController.java +++ b/backend/src/main/java/com/petshop/backend/controller/PetController.java @@ -1,3 +1,9 @@ +/* + * Handles managing pets available in the shop. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/PetImageController.java b/backend/src/main/java/com/petshop/backend/controller/PetImageController.java index 2cd9845f..8442e060 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PetImageController.java +++ b/backend/src/main/java/com/petshop/backend/controller/PetImageController.java @@ -1,3 +1,9 @@ +/* + * Handles uploading, retrieving, and deleting pet images. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.pet.PetResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductController.java b/backend/src/main/java/com/petshop/backend/controller/ProductController.java index 418d113a..acfc4fd4 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ProductController.java @@ -1,3 +1,9 @@ +/* + * Handles creating, updating, and browsing products in the shop. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductImageController.java b/backend/src/main/java/com/petshop/backend/controller/ProductImageController.java index fb9ffa08..de19b1fc 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ProductImageController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ProductImageController.java @@ -1,3 +1,9 @@ +/* + * Handles uploading, retrieving, and deleting product images. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.product.ProductResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java b/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java index f521cb76..c9f3a844 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java @@ -1,3 +1,9 @@ +/* + * Handles linking products to their suppliers. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.productsupplier.BulkDeleteProductSupplierRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java b/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java index c73d9fcc..9386ce14 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java +++ b/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java @@ -1,3 +1,9 @@ +/* + * Handles purchase orders for restocking products from suppliers. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; diff --git a/backend/src/main/java/com/petshop/backend/controller/RefundController.java b/backend/src/main/java/com/petshop/backend/controller/RefundController.java index b8e20241..399099de 100644 --- a/backend/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/backend/src/main/java/com/petshop/backend/controller/RefundController.java @@ -1,3 +1,9 @@ +/* + * Handles requesting and processing refunds for orders. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.refund.RefundRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/SaleController.java b/backend/src/main/java/com/petshop/backend/controller/SaleController.java index bc40b066..36039046 100644 --- a/backend/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/backend/src/main/java/com/petshop/backend/controller/SaleController.java @@ -1,3 +1,9 @@ +/* + * Handles creating and viewing sales and customer orders. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.sale.SaleRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java index 4789321a..3b42e11e 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -1,3 +1,9 @@ +/* + * Handles managing pet services like grooming and veterinary care. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/StoreController.java b/backend/src/main/java/com/petshop/backend/controller/StoreController.java index dc7bf8f5..2984597e 100644 --- a/backend/src/main/java/com/petshop/backend/controller/StoreController.java +++ b/backend/src/main/java/com/petshop/backend/controller/StoreController.java @@ -1,3 +1,9 @@ +/* + * Handles managing store locations for the pet shop. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/SupplierController.java b/backend/src/main/java/com/petshop/backend/controller/SupplierController.java index eb5c065d..9c0ff083 100644 --- a/backend/src/main/java/com/petshop/backend/controller/SupplierController.java +++ b/backend/src/main/java/com/petshop/backend/controller/SupplierController.java @@ -1,3 +1,9 @@ +/* + * Handles managing suppliers who provide products to the shop. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; diff --git a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java index ac7d146c..df453194 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java +++ b/backend/src/main/java/com/petshop/backend/controller/UserAvatarController.java @@ -1,3 +1,9 @@ +/* + * Handles uploading, retrieving, and deleting user profile pictures. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/controller/UserController.java b/backend/src/main/java/com/petshop/backend/controller/UserController.java index 28ef9f9e..ec28f50c 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserController.java +++ b/backend/src/main/java/com/petshop/backend/controller/UserController.java @@ -1,3 +1,9 @@ +/* + * Handles admin operations for managing all user accounts. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; -- 2.49.1 From 79951e02ee2e4016dc668b4ef5e3b169d851783f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 14:24:33 -0600 Subject: [PATCH 36/42] comment backend entities and repos --- .../main/java/com/petshop/backend/entity/ActivityLog.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Adoption.java | 7 +++++++ .../main/java/com/petshop/backend/entity/Appointment.java | 7 +++++++ backend/src/main/java/com/petshop/backend/entity/Cart.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/CartItem.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Category.java | 7 +++++++ .../main/java/com/petshop/backend/entity/Conversation.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Coupon.java | 7 +++++++ .../main/java/com/petshop/backend/entity/Inventory.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Message.java | 7 +++++++ .../com/petshop/backend/entity/PasswordResetToken.java | 7 +++++++ backend/src/main/java/com/petshop/backend/entity/Pet.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Product.java | 7 +++++++ .../java/com/petshop/backend/entity/ProductSupplier.java | 7 +++++++ .../java/com/petshop/backend/entity/PurchaseOrder.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Refund.java | 7 +++++++ .../main/java/com/petshop/backend/entity/RefundItem.java | 7 +++++++ backend/src/main/java/com/petshop/backend/entity/Sale.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/SaleItem.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Service.java | 7 +++++++ .../java/com/petshop/backend/entity/StoreLocation.java | 7 +++++++ .../src/main/java/com/petshop/backend/entity/Supplier.java | 7 +++++++ backend/src/main/java/com/petshop/backend/entity/User.java | 7 +++++++ .../petshop/backend/repository/ActivityLogRepository.java | 7 +++++++ .../com/petshop/backend/repository/AdoptionRepository.java | 7 +++++++ .../petshop/backend/repository/AppointmentRepository.java | 7 +++++++ .../com/petshop/backend/repository/CartItemRepository.java | 7 +++++++ .../com/petshop/backend/repository/CartRepository.java | 7 +++++++ .../com/petshop/backend/repository/CategoryRepository.java | 7 +++++++ .../petshop/backend/repository/ConversationRepository.java | 7 +++++++ .../com/petshop/backend/repository/CouponRepository.java | 7 +++++++ .../petshop/backend/repository/InventoryRepository.java | 7 +++++++ .../com/petshop/backend/repository/MessageRepository.java | 7 +++++++ .../backend/repository/PasswordResetTokenRepository.java | 7 +++++++ .../java/com/petshop/backend/repository/PetRepository.java | 7 +++++++ .../com/petshop/backend/repository/ProductRepository.java | 7 +++++++ .../backend/repository/ProductSupplierRepository.java | 7 +++++++ .../backend/repository/PurchaseOrderRepository.java | 7 +++++++ .../com/petshop/backend/repository/RefundRepository.java | 7 +++++++ .../com/petshop/backend/repository/SaleItemRepository.java | 7 +++++++ .../com/petshop/backend/repository/SaleRepository.java | 7 +++++++ .../com/petshop/backend/repository/ServiceRepository.java | 7 +++++++ .../com/petshop/backend/repository/StoreRepository.java | 7 +++++++ .../com/petshop/backend/repository/SupplierRepository.java | 7 +++++++ .../com/petshop/backend/repository/UserRepository.java | 7 +++++++ 45 files changed, 315 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java index b445b7c4..508c3ee6 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java +++ b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java @@ -1,3 +1,10 @@ +/* + * Represents an activity log entry in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Adoption.java b/backend/src/main/java/com/petshop/backend/entity/Adoption.java index e5dae5aa..fdc88a96 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/backend/src/main/java/com/petshop/backend/entity/Adoption.java @@ -1,3 +1,10 @@ +/* + * Represents a pet adoption record in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Appointment.java b/backend/src/main/java/com/petshop/backend/entity/Appointment.java index 0e80f58e..3ac8df7d 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/backend/src/main/java/com/petshop/backend/entity/Appointment.java @@ -1,3 +1,10 @@ +/* + * Represents an appointment in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Cart.java b/backend/src/main/java/com/petshop/backend/entity/Cart.java index 61066af6..65a25fa3 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Cart.java +++ b/backend/src/main/java/com/petshop/backend/entity/Cart.java @@ -1,3 +1,10 @@ +/* + * Represents a shopping cart in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/CartItem.java b/backend/src/main/java/com/petshop/backend/entity/CartItem.java index 1ad4c144..6268eaa6 100644 --- a/backend/src/main/java/com/petshop/backend/entity/CartItem.java +++ b/backend/src/main/java/com/petshop/backend/entity/CartItem.java @@ -1,3 +1,10 @@ +/* + * Represents a single item in a shopping cart. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Category.java b/backend/src/main/java/com/petshop/backend/entity/Category.java index ee79207e..a837a326 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Category.java +++ b/backend/src/main/java/com/petshop/backend/entity/Category.java @@ -1,3 +1,10 @@ +/* + * Represents a product category in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Conversation.java b/backend/src/main/java/com/petshop/backend/entity/Conversation.java index 080228eb..1a8d7d9b 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Conversation.java +++ b/backend/src/main/java/com/petshop/backend/entity/Conversation.java @@ -1,3 +1,10 @@ +/* + * Represents a chat conversation between a customer and staff. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Coupon.java b/backend/src/main/java/com/petshop/backend/entity/Coupon.java index ba234f12..667e3fd9 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Coupon.java +++ b/backend/src/main/java/com/petshop/backend/entity/Coupon.java @@ -1,3 +1,10 @@ +/* + * Represents a discount coupon in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Inventory.java b/backend/src/main/java/com/petshop/backend/entity/Inventory.java index ac859c64..836e06f2 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/backend/src/main/java/com/petshop/backend/entity/Inventory.java @@ -1,3 +1,10 @@ +/* + * Represents inventory stock for a product. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Message.java b/backend/src/main/java/com/petshop/backend/entity/Message.java index 7c7bc498..330b5649 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Message.java +++ b/backend/src/main/java/com/petshop/backend/entity/Message.java @@ -1,3 +1,10 @@ +/* + * Represents a single message in a conversation. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java b/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java index bd6c5494..03a9353b 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java +++ b/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java @@ -1,3 +1,10 @@ +/* + * Represents a password reset token for a user. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.Column; diff --git a/backend/src/main/java/com/petshop/backend/entity/Pet.java b/backend/src/main/java/com/petshop/backend/entity/Pet.java index 604a24db..94e47ce3 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Pet.java +++ b/backend/src/main/java/com/petshop/backend/entity/Pet.java @@ -1,3 +1,10 @@ +/* + * Represents a pet in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Product.java b/backend/src/main/java/com/petshop/backend/entity/Product.java index 84c17c1a..e78f4c2e 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Product.java +++ b/backend/src/main/java/com/petshop/backend/entity/Product.java @@ -1,3 +1,10 @@ +/* + * Represents a product in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java b/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java index 95aa6017..f85189ad 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java +++ b/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java @@ -1,3 +1,10 @@ +/* + * Represents the link between a product and its supplier. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java index fd50c120..f417c1f0 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -1,3 +1,10 @@ +/* + * Represents a purchase order placed with a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Refund.java b/backend/src/main/java/com/petshop/backend/entity/Refund.java index f6b9b45d..92e3a8d9 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Refund.java +++ b/backend/src/main/java/com/petshop/backend/entity/Refund.java @@ -1,3 +1,10 @@ +/* + * Represents a refund for a sale. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/RefundItem.java b/backend/src/main/java/com/petshop/backend/entity/RefundItem.java index 30e675c2..989dd1f2 100644 --- a/backend/src/main/java/com/petshop/backend/entity/RefundItem.java +++ b/backend/src/main/java/com/petshop/backend/entity/RefundItem.java @@ -1,3 +1,10 @@ +/* + * Represents a single item in a refund. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java index d1134b58..19f687e6 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -1,3 +1,10 @@ +/* + * Represents a sale transaction in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/SaleItem.java b/backend/src/main/java/com/petshop/backend/entity/SaleItem.java index b80ab370..25b47ddf 100644 --- a/backend/src/main/java/com/petshop/backend/entity/SaleItem.java +++ b/backend/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -1,3 +1,10 @@ +/* + * Represents a single item in a sale. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/Service.java b/backend/src/main/java/com/petshop/backend/entity/Service.java index 6b6c4267..f669b1b7 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Service.java +++ b/backend/src/main/java/com/petshop/backend/entity/Service.java @@ -1,3 +1,10 @@ +/* + * Represents a pet service offered by the store. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import jakarta.persistence.*; diff --git a/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java b/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java index 1f79a830..9ba26619 100644 --- a/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java +++ b/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java @@ -1,3 +1,10 @@ +/* + * Represents a store location in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import com.petshop.backend.util.PhoneUtils; diff --git a/backend/src/main/java/com/petshop/backend/entity/Supplier.java b/backend/src/main/java/com/petshop/backend/entity/Supplier.java index 911aad08..e1abde9c 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Supplier.java +++ b/backend/src/main/java/com/petshop/backend/entity/Supplier.java @@ -1,3 +1,10 @@ +/* + * Represents a supplier in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import com.petshop.backend.util.PhoneUtils; diff --git a/backend/src/main/java/com/petshop/backend/entity/User.java b/backend/src/main/java/com/petshop/backend/entity/User.java index 97b8dfc5..abb36bcd 100644 --- a/backend/src/main/java/com/petshop/backend/entity/User.java +++ b/backend/src/main/java/com/petshop/backend/entity/User.java @@ -1,3 +1,10 @@ +/* + * Represents a user account in the database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.entity; import com.petshop.backend.util.PhoneUtils; diff --git a/backend/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java b/backend/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java index 09bf817a..f9bae4eb 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for activity logs. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.ActivityLog; 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 ba351042..1f919e90 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for adoptions. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Adoption; diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 774c1427..2feb77be 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for appointments. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Appointment; diff --git a/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java b/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java index f610233c..01cb42e9 100644 --- a/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for cart items. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.CartItem; diff --git a/backend/src/main/java/com/petshop/backend/repository/CartRepository.java b/backend/src/main/java/com/petshop/backend/repository/CartRepository.java index ab5d816a..521b6399 100644 --- a/backend/src/main/java/com/petshop/backend/repository/CartRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/CartRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for shopping carts. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Cart; diff --git a/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java b/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java index b1871d1f..1819ebbb 100644 --- a/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for categories. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Category; diff --git a/backend/src/main/java/com/petshop/backend/repository/ConversationRepository.java b/backend/src/main/java/com/petshop/backend/repository/ConversationRepository.java index 9ec1fde9..34af3e45 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ConversationRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ConversationRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for conversations. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Conversation; diff --git a/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java b/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java index 0034a887..8f283821 100644 --- a/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for coupons. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Coupon; diff --git a/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java b/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java index 82f3c797..53a9cb69 100644 --- a/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for inventory records. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Inventory; diff --git a/backend/src/main/java/com/petshop/backend/repository/MessageRepository.java b/backend/src/main/java/com/petshop/backend/repository/MessageRepository.java index 6c87be6e..6e039d9a 100644 --- a/backend/src/main/java/com/petshop/backend/repository/MessageRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/MessageRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for messages. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Message; diff --git a/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java b/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java index e436fb59..df8e5bae 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for password reset tokens. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.PasswordResetToken; diff --git a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java index c5f8d73a..1e395895 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for pets. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Pet; diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java index 140031d0..ef45c25f 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for products. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Product; diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java b/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java index 15d17703..fa824b19 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for product-supplier links. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.ProductSupplier; diff --git a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java index 5ebf9457..a914c934 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for purchase orders. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.PurchaseOrder; diff --git a/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java b/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java index fe507c3f..7fb00539 100644 --- a/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for refunds. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Refund; diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java index 0b67f95e..d5ed04aa 100644 --- a/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for sale items. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.SaleItem; diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java index 19a93ee9..61e4cd69 100644 --- a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for sales. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Sale; diff --git a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java index e6271dfd..3f3509bd 100644 --- a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for services. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Service; diff --git a/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java b/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java index 5ee3758b..2733fb1a 100644 --- a/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for store locations. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.StoreLocation; diff --git a/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java b/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java index c7dd2307..690beebf 100644 --- a/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for suppliers. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.Supplier; diff --git a/backend/src/main/java/com/petshop/backend/repository/UserRepository.java b/backend/src/main/java/com/petshop/backend/repository/UserRepository.java index 022a3162..f2b0155c 100644 --- a/backend/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -1,3 +1,10 @@ +/* + * Database queries for users. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.repository; import com.petshop.backend.entity.User; -- 2.49.1 From 38f7852999fd49be140d4b0e84d47649706966c7 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 15:18:54 -0600 Subject: [PATCH 37/42] comment backend DTOs --- .../petshop/backend/dto/activity/ActivityLogResponse.java | 6 ++++++ .../com/petshop/backend/dto/adoption/AdoptionRequest.java | 6 ++++++ .../com/petshop/backend/dto/adoption/AdoptionResponse.java | 6 ++++++ .../backend/dto/adoption/CustomerAdoptionRequest.java | 6 ++++++ .../main/java/com/petshop/backend/dto/ai/AiChatRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/ai/AiChatResponse.java | 6 ++++++ .../petshop/backend/dto/analytics/DashboardResponse.java | 6 ++++++ .../petshop/backend/dto/appointment/AppointmentRequest.java | 6 ++++++ .../backend/dto/appointment/AppointmentResponse.java | 6 ++++++ .../com/petshop/backend/dto/auth/AvatarUploadResponse.java | 6 ++++++ .../com/petshop/backend/dto/auth/ForgotPasswordRequest.java | 6 ++++++ .../petshop/backend/dto/auth/ForgotPasswordResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/auth/LoginRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/auth/LoginResponse.java | 6 ++++++ .../com/petshop/backend/dto/auth/ProfileUpdateRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/auth/RegisterRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/auth/RegisterResponse.java | 6 ++++++ .../com/petshop/backend/dto/auth/ResetPasswordRequest.java | 6 ++++++ .../com/petshop/backend/dto/auth/ResetPasswordResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/auth/UserInfoResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/cart/AddToCartRequest.java | 6 ++++++ .../com/petshop/backend/dto/cart/ApplyCouponRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/cart/CartItemResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/cart/CartResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/cart/CheckoutRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/cart/CheckoutResponse.java | 6 ++++++ .../com/petshop/backend/dto/cart/UpdateCartItemRequest.java | 6 ++++++ .../com/petshop/backend/dto/category/CategoryRequest.java | 6 ++++++ .../com/petshop/backend/dto/category/CategoryResponse.java | 6 ++++++ .../com/petshop/backend/dto/chat/ConversationRequest.java | 6 ++++++ .../com/petshop/backend/dto/chat/ConversationResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/chat/MessageRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/chat/MessageResponse.java | 6 ++++++ .../petshop/backend/dto/chat/UpdateConversationRequest.java | 6 ++++++ .../com/petshop/backend/dto/common/BulkDeleteRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/common/CouponRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/common/CouponResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/common/DropdownOption.java | 6 ++++++ .../com/petshop/backend/dto/inventory/InventoryRequest.java | 6 ++++++ .../petshop/backend/dto/inventory/InventoryResponse.java | 6 ++++++ .../main/java/com/petshop/backend/dto/pet/MyPetRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/pet/MyPetResponse.java | 6 ++++++ .../main/java/com/petshop/backend/dto/pet/PetRequest.java | 6 ++++++ .../main/java/com/petshop/backend/dto/pet/PetResponse.java | 6 ++++++ .../com/petshop/backend/dto/product/ProductRequest.java | 6 ++++++ .../com/petshop/backend/dto/product/ProductResponse.java | 6 ++++++ .../productsupplier/BulkDeleteProductSupplierRequest.java | 6 ++++++ .../backend/dto/productsupplier/ProductSupplierKey.java | 6 ++++++ .../backend/dto/productsupplier/ProductSupplierRequest.java | 6 ++++++ .../dto/productsupplier/ProductSupplierResponse.java | 6 ++++++ .../backend/dto/purchaseorder/PurchaseOrderResponse.java | 6 ++++++ .../com/petshop/backend/dto/refund/RefundItemResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/refund/RefundRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/refund/RefundResponse.java | 6 ++++++ .../com/petshop/backend/dto/refund/RefundUpdateRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/sale/SaleItemRequest.java | 6 ++++++ .../main/java/com/petshop/backend/dto/sale/SaleRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/sale/SaleResponse.java | 6 ++++++ .../com/petshop/backend/dto/service/ServiceRequest.java | 6 ++++++ .../com/petshop/backend/dto/service/ServiceResponse.java | 6 ++++++ .../java/com/petshop/backend/dto/store/StoreRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/store/StoreResponse.java | 6 ++++++ .../com/petshop/backend/dto/supplier/SupplierRequest.java | 6 ++++++ .../com/petshop/backend/dto/supplier/SupplierResponse.java | 6 ++++++ .../main/java/com/petshop/backend/dto/user/UserRequest.java | 6 ++++++ .../java/com/petshop/backend/dto/user/UserResponse.java | 6 ++++++ 66 files changed, 396 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/dto/activity/ActivityLogResponse.java b/backend/src/main/java/com/petshop/backend/dto/activity/ActivityLogResponse.java index 3d5bc890..0139a12d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/activity/ActivityLogResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/activity/ActivityLogResponse.java @@ -1,3 +1,9 @@ +/* + * Activity log entry returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.activity; import java.time.LocalDateTime; 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 0a45996f..1a3385f4 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 @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating an adoption. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.adoption; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java index 7b8e821b..cadcc199 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -1,3 +1,9 @@ +/* + * Adoption record returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.adoption; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java b/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java index 4272b2db..ef20e1f3 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/CustomerAdoptionRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a customer requests to adopt a pet. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.adoption; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/petshop/backend/dto/ai/AiChatRequest.java b/backend/src/main/java/com/petshop/backend/dto/ai/AiChatRequest.java index b043b27c..a147b7ba 100644 --- a/backend/src/main/java/com/petshop/backend/dto/ai/AiChatRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/ai/AiChatRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when asking the AI chatbot a question. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.ai; import java.util.List; diff --git a/backend/src/main/java/com/petshop/backend/dto/ai/AiChatResponse.java b/backend/src/main/java/com/petshop/backend/dto/ai/AiChatResponse.java index 05f310b4..93f6a7a9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/ai/AiChatResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/ai/AiChatResponse.java @@ -1,3 +1,9 @@ +/* + * AI chatbot reply returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.ai; public class AiChatResponse { diff --git a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java index 56788f77..49768b4b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -1,3 +1,9 @@ +/* + * Dashboard analytics data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.analytics; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index d1c3847a..810f4a92 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when booking or updating an appointment. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.appointment; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index b2e9a9ba..274c7d39 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -1,3 +1,9 @@ +/* + * Appointment details returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.appointment; import java.time.LocalDate; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java index 8e75af96..b2e07935 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java @@ -1,3 +1,9 @@ +/* + * Avatar upload result returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java index 529581cc..39539be7 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a user requests a password reset. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java index 6062a196..d9d22253 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java @@ -1,3 +1,9 @@ +/* + * Forgot password result returned with a reset token. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; public class ForgotPasswordResponse { diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java index 970c4719..37f734c9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a user logs in. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java index e3d01c11..c546e002 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java @@ -1,3 +1,9 @@ +/* + * Login result returned to the client with a token. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java index 97b978b0..422a6bd2 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a user updates their profile. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.Email; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java index 4758d923..d4d81a9d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a new user registers an account. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.Email; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java index 7e016985..91c59b72 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java @@ -1,3 +1,9 @@ +/* + * Registration result returned to the client after signing up. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java index 3f20f7f7..5c1ee5a6 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a user sets a new password using a reset token. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java index 0edf18a7..0522d527 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java @@ -1,3 +1,9 @@ +/* + * Password reset confirmation returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; public class ResetPasswordResponse { diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 74dbc039..34e980eb 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -1,3 +1,9 @@ +/* + * Logged-in user profile data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.auth; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/AddToCartRequest.java b/backend/src/main/java/com/petshop/backend/dto/cart/AddToCartRequest.java index 6075ad4c..666aabb8 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/AddToCartRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/AddToCartRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when adding a product to the shopping cart. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import jakarta.validation.constraints.Min; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/ApplyCouponRequest.java b/backend/src/main/java/com/petshop/backend/dto/cart/ApplyCouponRequest.java index 290d4434..b7143dbe 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/ApplyCouponRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/ApplyCouponRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when applying a coupon code to the cart. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/CartItemResponse.java b/backend/src/main/java/com/petshop/backend/dto/cart/CartItemResponse.java index 6b13e1bc..e3476652 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/CartItemResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/CartItemResponse.java @@ -1,3 +1,9 @@ +/* + * Single cart item returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java b/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java index 58ff0444..3e3f7d4a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java @@ -1,3 +1,9 @@ +/* + * Full shopping cart data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutRequest.java b/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutRequest.java index 856582ed..93960872 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a customer checks out their cart. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutResponse.java b/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutResponse.java index c703fbc0..d5b7a829 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/CheckoutResponse.java @@ -1,3 +1,9 @@ +/* + * Checkout result returned to the client after placing an order. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/UpdateCartItemRequest.java b/backend/src/main/java/com/petshop/backend/dto/cart/UpdateCartItemRequest.java index 205aaa4a..b831939c 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/UpdateCartItemRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/UpdateCartItemRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when updating the quantity of a cart item. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.cart; import jakarta.validation.constraints.Min; diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java index b099826f..0ad382cc 100644 --- a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a category. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.category; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java b/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java index 6d9e1569..93f1e459 100644 --- a/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java @@ -1,3 +1,9 @@ +/* + * Category data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.category; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java index 8677865a..1c9d833d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when starting a new conversation. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.chat; public class ConversationRequest { diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java index cef833cd..bd8bec78 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java @@ -1,3 +1,9 @@ +/* + * Conversation details returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.chat; import com.petshop.backend.entity.Conversation; diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java index 0784054b..e7900aab 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when sending a chat message. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.chat; public class MessageRequest { diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java b/backend/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java index 146f0229..2a602cc2 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java @@ -1,3 +1,9 @@ +/* + * Chat message returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.chat; import com.petshop.backend.entity.Message; diff --git a/backend/src/main/java/com/petshop/backend/dto/chat/UpdateConversationRequest.java b/backend/src/main/java/com/petshop/backend/dto/chat/UpdateConversationRequest.java index 4c043a79..d3125659 100644 --- a/backend/src/main/java/com/petshop/backend/dto/chat/UpdateConversationRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/chat/UpdateConversationRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when changing the status of a conversation. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.chat; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java b/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java index bec920f1..203bf7c6 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when deleting multiple records at once. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.common; import jakarta.validation.constraints.NotEmpty; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java b/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java index ffa7f766..1817cc69 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/common/CouponRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a coupon. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.common; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/CouponResponse.java b/backend/src/main/java/com/petshop/backend/dto/common/CouponResponse.java index 9d763817..2221c92a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/CouponResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/common/CouponResponse.java @@ -1,3 +1,9 @@ +/* + * Coupon details returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.common; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java b/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java index 6ea0d058..f655fb5b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java +++ b/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java @@ -1,3 +1,9 @@ +/* + * Simple id and label pair used for dropdown menus. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.common; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java index 7ad02d3a..5b3c4ef0 100644 --- a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when updating inventory for a product. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.inventory; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java index 5879a554..d208fde9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java @@ -1,3 +1,9 @@ +/* + * Inventory record returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.inventory; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java index 37942a47..0daafc98 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when a customer adds or updates their own pet. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.pet; import jakarta.validation.constraints.Max; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java index 15117334..2c03726f 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java @@ -1,3 +1,9 @@ +/* + * Customer's own pet data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.pet; public class MyPetResponse { diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java index 4d0986bb..cc7b693a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a pet listing. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.pet; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java index b7113bb1..fd6b7cc9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java @@ -1,3 +1,9 @@ +/* + * Pet data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.pet; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java index e1ca9241..f5bd69f7 100644 --- a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a product. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.product; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java b/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java index c08abf9a..df11c90b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java @@ -1,3 +1,9 @@ +/* + * Product data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.product; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java index 7eb8a3cc..c2b488e0 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when deleting multiple product-supplier links at once. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotEmpty; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java index 557dbeda..8f819c0b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -1,3 +1,9 @@ +/* + * Composite key identifying a product-supplier relationship. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.productsupplier; import java.util.Objects; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java index 1bc05210..3d7dd214 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when linking a product to a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java index 4190dabb..0aac04d1 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java @@ -1,3 +1,9 @@ +/* + * Product-supplier link data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.productsupplier; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index 98f81914..3a91e113 100644 --- a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -1,3 +1,9 @@ +/* + * Purchase order data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.purchaseorder; import java.time.LocalDate; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemResponse.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemResponse.java index 8c33c168..b033202b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemResponse.java @@ -1,3 +1,9 @@ +/* + * Single refunded item returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.refund; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java index 37121047..216a1be5 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when requesting a refund for a sale. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.refund; import com.petshop.backend.dto.sale.SaleItemRequest; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java index dc469dd4..0c356e63 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -1,3 +1,9 @@ +/* + * Refund record returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.refund; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java index 22dddf95..dbd458ff 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when updating the status of a refund. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.refund; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java index dad7011a..ea3cf486 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java @@ -1,3 +1,9 @@ +/* + * Single line item included in a sale request. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.sale; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index b0e7989f..c81d5fdb 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when recording a new sale. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.sale; import jakarta.validation.Valid; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index af116212..4fdbe968 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -1,3 +1,9 @@ +/* + * Sale record returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.sale; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java index 464fefda..390eece7 100644 --- a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a service. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.service; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java b/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java index 4f0300ab..b68d186d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java @@ -1,3 +1,9 @@ +/* + * Service data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.service; import java.math.BigDecimal; diff --git a/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java b/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java index fca03db1..b849023a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/store/StoreRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a store. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.store; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/store/StoreResponse.java b/backend/src/main/java/com/petshop/backend/dto/store/StoreResponse.java index bf5cafd2..12c8be06 100644 --- a/backend/src/main/java/com/petshop/backend/dto/store/StoreResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/store/StoreResponse.java @@ -1,3 +1,9 @@ +/* + * Store data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.store; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index a0eb2b6a..5efd27d0 100644 --- a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.supplier; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java index d23b4a2b..d382f61d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java @@ -1,3 +1,9 @@ +/* + * Supplier data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.supplier; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java index 04c82e43..491ace3d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -1,3 +1,9 @@ +/* + * Data sent when creating or updating a user account. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.user; import com.petshop.backend.util.SafeContent; diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java index 6c8ffd68..9cc8d6f1 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -1,3 +1,9 @@ +/* + * User account data returned to the client. + * + * Author: Harkamal + * Date: April 2026 + */ package com.petshop.backend.dto.user; import java.time.LocalDateTime; -- 2.49.1 From c8e7f83098c4434fff119ffba76f5c0714eecedb Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 15:43:11 -0600 Subject: [PATCH 38/42] comment backend events and utils --- .../main/java/com/petshop/backend/BackendApplication.java | 7 +++++++ .../main/java/com/petshop/backend/DevStackApplication.java | 7 +++++++ .../java/com/petshop/backend/DockerComposeSupport.java | 7 +++++++ .../main/java/com/petshop/backend/PortCleanupSupport.java | 7 +++++++ .../java/com/petshop/backend/ResetDatabaseApplication.java | 7 +++++++ .../com/petshop/backend/RuntimeClasspathValidator.java | 7 +++++++ .../com/petshop/backend/event/AdoptionConfirmedEvent.java | 7 +++++++ .../com/petshop/backend/event/AdoptionReminderEvent.java | 7 +++++++ .../petshop/backend/event/AppointmentConfirmedEvent.java | 7 +++++++ .../petshop/backend/event/AppointmentReminderEvent.java | 7 +++++++ .../java/com/petshop/backend/event/EmailEventListener.java | 7 +++++++ .../java/com/petshop/backend/event/SaleReceiptEvent.java | 7 +++++++ .../com/petshop/backend/exception/ApiErrorResponder.java | 7 +++++++ .../com/petshop/backend/exception/ApiErrorResponse.java | 7 +++++++ .../com/petshop/backend/exception/BusinessException.java | 7 +++++++ .../com/petshop/backend/exception/ConflictException.java | 7 +++++++ .../petshop/backend/exception/GlobalExceptionHandler.java | 7 +++++++ .../backend/exception/ResourceNotFoundException.java | 7 +++++++ .../com/petshop/backend/util/AuthenticationHelper.java | 7 +++++++ .../main/java/com/petshop/backend/util/ContentFilter.java | 7 +++++++ .../java/com/petshop/backend/util/ImageValidationUtil.java | 7 +++++++ .../src/main/java/com/petshop/backend/util/PhoneUtils.java | 7 +++++++ .../main/java/com/petshop/backend/util/SafeContent.java | 7 +++++++ .../com/petshop/backend/util/SafeContentValidator.java | 7 +++++++ .../main/java/com/petshop/backend/util/StringUtils.java | 7 +++++++ 25 files changed, 175 insertions(+) diff --git a/backend/src/main/java/com/petshop/backend/BackendApplication.java b/backend/src/main/java/com/petshop/backend/BackendApplication.java index 584a7fd0..0731544b 100644 --- a/backend/src/main/java/com/petshop/backend/BackendApplication.java +++ b/backend/src/main/java/com/petshop/backend/BackendApplication.java @@ -1,3 +1,10 @@ +/* + * Main entry point for the pet shop backend application. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; import com.petshop.backend.config.BusinessProperties; diff --git a/backend/src/main/java/com/petshop/backend/DevStackApplication.java b/backend/src/main/java/com/petshop/backend/DevStackApplication.java index 27933d4b..8effc5cb 100644 --- a/backend/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/backend/src/main/java/com/petshop/backend/DevStackApplication.java @@ -1,3 +1,10 @@ +/* + * Starts the backend with Docker and auto-reload for development. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; import com.petshop.backend.config.FlywayContextInitializer; diff --git a/backend/src/main/java/com/petshop/backend/DockerComposeSupport.java b/backend/src/main/java/com/petshop/backend/DockerComposeSupport.java index 4832de9b..4f02ced2 100644 --- a/backend/src/main/java/com/petshop/backend/DockerComposeSupport.java +++ b/backend/src/main/java/com/petshop/backend/DockerComposeSupport.java @@ -1,3 +1,10 @@ +/* + * Manages Docker Compose for the development database. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; import java.io.BufferedReader; diff --git a/backend/src/main/java/com/petshop/backend/PortCleanupSupport.java b/backend/src/main/java/com/petshop/backend/PortCleanupSupport.java index c6cb5d9b..fb941119 100644 --- a/backend/src/main/java/com/petshop/backend/PortCleanupSupport.java +++ b/backend/src/main/java/com/petshop/backend/PortCleanupSupport.java @@ -1,3 +1,10 @@ +/* + * Frees up a port by stopping whatever process is using it. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; import java.io.BufferedReader; diff --git a/backend/src/main/java/com/petshop/backend/ResetDatabaseApplication.java b/backend/src/main/java/com/petshop/backend/ResetDatabaseApplication.java index 72c43ac7..6bb9c011 100644 --- a/backend/src/main/java/com/petshop/backend/ResetDatabaseApplication.java +++ b/backend/src/main/java/com/petshop/backend/ResetDatabaseApplication.java @@ -1,3 +1,10 @@ +/* + * Resets the development database by recreating the Docker container. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; public class ResetDatabaseApplication { diff --git a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java index e123f66a..2b59bd93 100644 --- a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java +++ b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java @@ -1,3 +1,10 @@ +/* + * Checks that required resources are on the classpath at startup. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend; import java.net.URL; diff --git a/backend/src/main/java/com/petshop/backend/event/AdoptionConfirmedEvent.java b/backend/src/main/java/com/petshop/backend/event/AdoptionConfirmedEvent.java index 87dad0ee..68a8fa77 100644 --- a/backend/src/main/java/com/petshop/backend/event/AdoptionConfirmedEvent.java +++ b/backend/src/main/java/com/petshop/backend/event/AdoptionConfirmedEvent.java @@ -1,3 +1,10 @@ +/* + * Event fired when an adoption is confirmed. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; public record AdoptionConfirmedEvent(Long adoptionId) {} diff --git a/backend/src/main/java/com/petshop/backend/event/AdoptionReminderEvent.java b/backend/src/main/java/com/petshop/backend/event/AdoptionReminderEvent.java index bfc3be52..0130031f 100644 --- a/backend/src/main/java/com/petshop/backend/event/AdoptionReminderEvent.java +++ b/backend/src/main/java/com/petshop/backend/event/AdoptionReminderEvent.java @@ -1,3 +1,10 @@ +/* + * Event fired to send an adoption reminder email. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; public record AdoptionReminderEvent(Long adoptionId) {} diff --git a/backend/src/main/java/com/petshop/backend/event/AppointmentConfirmedEvent.java b/backend/src/main/java/com/petshop/backend/event/AppointmentConfirmedEvent.java index 9311e8dc..7a63a5d9 100644 --- a/backend/src/main/java/com/petshop/backend/event/AppointmentConfirmedEvent.java +++ b/backend/src/main/java/com/petshop/backend/event/AppointmentConfirmedEvent.java @@ -1,3 +1,10 @@ +/* + * Event fired when an appointment is confirmed. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; public record AppointmentConfirmedEvent(Long appointmentId) {} diff --git a/backend/src/main/java/com/petshop/backend/event/AppointmentReminderEvent.java b/backend/src/main/java/com/petshop/backend/event/AppointmentReminderEvent.java index c8de75ce..c7112a30 100644 --- a/backend/src/main/java/com/petshop/backend/event/AppointmentReminderEvent.java +++ b/backend/src/main/java/com/petshop/backend/event/AppointmentReminderEvent.java @@ -1,3 +1,10 @@ +/* + * Event fired to send an appointment reminder email. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; public record AppointmentReminderEvent(Long appointmentId) {} diff --git a/backend/src/main/java/com/petshop/backend/event/EmailEventListener.java b/backend/src/main/java/com/petshop/backend/event/EmailEventListener.java index 71e0cbf6..ad9808a7 100644 --- a/backend/src/main/java/com/petshop/backend/event/EmailEventListener.java +++ b/backend/src/main/java/com/petshop/backend/event/EmailEventListener.java @@ -1,3 +1,10 @@ +/* + * Listens for events and sends the matching emails. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; import com.petshop.backend.repository.AdoptionRepository; diff --git a/backend/src/main/java/com/petshop/backend/event/SaleReceiptEvent.java b/backend/src/main/java/com/petshop/backend/event/SaleReceiptEvent.java index d21df856..4390b79e 100644 --- a/backend/src/main/java/com/petshop/backend/event/SaleReceiptEvent.java +++ b/backend/src/main/java/com/petshop/backend/event/SaleReceiptEvent.java @@ -1,3 +1,10 @@ +/* + * Event fired to send a sale receipt email. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.event; public record SaleReceiptEvent(Long saleId) {} diff --git a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java index 0fcb82ec..9790a8e6 100644 --- a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java +++ b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java @@ -1,3 +1,10 @@ +/* + * Writes error responses to the client in a consistent format. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java index b3aea542..4f63487a 100644 --- a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java +++ b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java @@ -1,3 +1,10 @@ +/* + * Holds the structure of an error response sent to the client. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/petshop/backend/exception/BusinessException.java b/backend/src/main/java/com/petshop/backend/exception/BusinessException.java index 005ee62b..b42f425b 100644 --- a/backend/src/main/java/com/petshop/backend/exception/BusinessException.java +++ b/backend/src/main/java/com/petshop/backend/exception/BusinessException.java @@ -1,3 +1,10 @@ +/* + * Thrown when a business rule is violated. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; public class BusinessException extends RuntimeException { diff --git a/backend/src/main/java/com/petshop/backend/exception/ConflictException.java b/backend/src/main/java/com/petshop/backend/exception/ConflictException.java index a0112782..cbbfecc4 100644 --- a/backend/src/main/java/com/petshop/backend/exception/ConflictException.java +++ b/backend/src/main/java/com/petshop/backend/exception/ConflictException.java @@ -1,3 +1,10 @@ +/* + * Thrown when a request conflicts with existing data. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; public class ConflictException extends RuntimeException { diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index ed070aed..f68d5c93 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -1,3 +1,10 @@ +/* + * Catches all exceptions and returns proper error responses. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; import com.petshop.backend.service.PetService; diff --git a/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java b/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java index 08b426b7..f9d2126c 100644 --- a/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java +++ b/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java @@ -1,3 +1,10 @@ +/* + * Thrown when a requested resource does not exist. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.exception; public class ResourceNotFoundException extends RuntimeException { diff --git a/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java b/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java index 71f041ab..e63d048e 100644 --- a/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java +++ b/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -1,3 +1,10 @@ +/* + * Retrieves the currently logged-in user from the security context. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; import com.petshop.backend.entity.User; diff --git a/backend/src/main/java/com/petshop/backend/util/ContentFilter.java b/backend/src/main/java/com/petshop/backend/util/ContentFilter.java index 0552b2d2..8f1e4ec5 100644 --- a/backend/src/main/java/com/petshop/backend/util/ContentFilter.java +++ b/backend/src/main/java/com/petshop/backend/util/ContentFilter.java @@ -1,3 +1,10 @@ +/* + * Checks user input for scripts and inappropriate language. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; import com.petshop.backend.exception.BusinessException; diff --git a/backend/src/main/java/com/petshop/backend/util/ImageValidationUtil.java b/backend/src/main/java/com/petshop/backend/util/ImageValidationUtil.java index 4ab26d2b..c5d1ef05 100644 --- a/backend/src/main/java/com/petshop/backend/util/ImageValidationUtil.java +++ b/backend/src/main/java/com/petshop/backend/util/ImageValidationUtil.java @@ -1,3 +1,10 @@ +/* + * Validates uploaded image files for type and size. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; import com.petshop.backend.exception.BusinessException; diff --git a/backend/src/main/java/com/petshop/backend/util/PhoneUtils.java b/backend/src/main/java/com/petshop/backend/util/PhoneUtils.java index cf9646ba..8dd979e0 100644 --- a/backend/src/main/java/com/petshop/backend/util/PhoneUtils.java +++ b/backend/src/main/java/com/petshop/backend/util/PhoneUtils.java @@ -1,3 +1,10 @@ +/* + * Formats and normalizes phone numbers. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; public class PhoneUtils { diff --git a/backend/src/main/java/com/petshop/backend/util/SafeContent.java b/backend/src/main/java/com/petshop/backend/util/SafeContent.java index 6db91b6e..bd703239 100644 --- a/backend/src/main/java/com/petshop/backend/util/SafeContent.java +++ b/backend/src/main/java/com/petshop/backend/util/SafeContent.java @@ -1,3 +1,10 @@ +/* + * Annotation that marks a field for content safety validation. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; import jakarta.validation.Constraint; diff --git a/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java b/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java index 6d3af630..72c26fc1 100644 --- a/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java +++ b/backend/src/main/java/com/petshop/backend/util/SafeContentValidator.java @@ -1,3 +1,10 @@ +/* + * Validates fields annotated with SafeContent for dangerous input. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; import jakarta.validation.ConstraintValidator; diff --git a/backend/src/main/java/com/petshop/backend/util/StringUtils.java b/backend/src/main/java/com/petshop/backend/util/StringUtils.java index fd795822..19f58b62 100644 --- a/backend/src/main/java/com/petshop/backend/util/StringUtils.java +++ b/backend/src/main/java/com/petshop/backend/util/StringUtils.java @@ -1,3 +1,10 @@ +/* + * Helper methods for trimming and formatting strings. + * + * Author: Harkamal + * Date: April 2026 + */ + package com.petshop.backend.util; public final class StringUtils { -- 2.49.1 From 4db234388ae72e4639747b345caec688951c52f4 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 16:22:39 -0600 Subject: [PATCH 39/42] comment desktop controllers --- .../controllers/ActivityLogController.java | 6 +++ .../controllers/AdoptionController.java | 6 +++ .../controllers/AnalyticsController.java | 6 +++ .../controllers/AppointmentController.java | 25 ++++++++++ .../controllers/ChatController.java | 47 ++++++++++++++++++ .../controllers/CouponController.java | 6 +++ .../CustomerAccountsController.java | 6 +++ .../controllers/InventoryController.java | 6 +++ .../controllers/LoginController.java | 6 +++ .../controllers/MainLayoutController.java | 37 ++++++++++++++ .../controllers/PetController.java | 6 +++ .../controllers/ProductController.java | 6 +++ .../ProductSupplierController.java | 6 +++ .../controllers/PurchaseOrderController.java | 6 +++ .../controllers/SaleController.java | 49 +++++++++++++++++++ .../controllers/ServiceController.java | 6 +++ .../controllers/StaffAccountsController.java | 6 +++ .../controllers/SupplierController.java | 6 +++ .../AdoptionDialogController.java | 6 +++ .../AppointmentDialogController.java | 6 +++ .../CouponDialogController.java | 6 +++ .../CustomerEditDialogController.java | 6 +++ .../InventoryDialogController.java | 6 +++ .../PetDialogController.java | 6 +++ .../ProductDialogController.java | 6 +++ .../ProductSupplierDialogController.java | 6 +++ .../PurchaseOrderDetailsDialogController.java | 6 +++ .../RefundDialogController.java | 6 +++ .../SaleDetailDialogController.java | 6 +++ .../ServiceDialogController.java | 6 +++ .../StaffEditDialogController.java | 6 +++ .../StaffRegisterDialogController.java | 6 +++ .../SupplierDialogController.java | 6 +++ 33 files changed, 332 insertions(+) diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java index 06f94874..25f89ecf 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java @@ -1,3 +1,9 @@ +/* + * Controls the activity log screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java index 3ab3c712..bd761459 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java @@ -1,3 +1,9 @@ +/* + * Controls the pet adoption management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AnalyticsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AnalyticsController.java index 830ff827..60b031d1 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AnalyticsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AnalyticsController.java @@ -1,3 +1,9 @@ +/* + * Controls the analytics and reporting screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; 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 a5a40c39..8d8b55b1 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -1,3 +1,9 @@ +/* + * Controls the appointment scheduling screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; @@ -132,6 +138,10 @@ public class AppointmentController { 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(){ new Thread(() -> { try{ @@ -165,6 +175,11 @@ public class AppointmentController { }).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) { String query = text == null || text.trim().isEmpty() ? null : text.trim(); new Thread(() -> { @@ -334,6 +349,10 @@ public class AppointmentController { 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() { String selectedStatus = cbStatusFilter.getValue(); 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) { if (status == null) { return "Booked"; 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 3ba9e995..66ef1f47 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java @@ -1,3 +1,9 @@ +/* + * Controls the in-app messaging screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; @@ -105,6 +111,8 @@ public class ChatController { lvActiveConversations.setItems(activeConversations); 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) -> { if (newVal != null && !selectionChanging) { 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;"); } + /** + * 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 lv) { lv.setFixedCellSize(CHAT_CELL_HEIGHT); lv.setCellFactory(list -> new ListCell<>() { @@ -453,6 +466,12 @@ public class ChatController { 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) { try { 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) { Optional existing = conversations.stream() .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() { if (selectedConversation == null) { 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) { boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId()); @@ -609,6 +643,13 @@ public class ChatController { 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) { if ("BOT".equalsIgnoreCase(message.getSenderRole())) { return message.getSenderDisplayName() != null && !message.getSenderDisplayName().isBlank() @@ -630,6 +671,12 @@ public class ChatController { 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) { String updated = conversation.getUpdatedAt() == null ? "" : TIME_FORMATTER.format(conversation.getUpdatedAt()); if ("CLOSED".equals(conversation.getStatus())) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java index af418826..3dd02612 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CouponController.java @@ -1,3 +1,9 @@ +/* + * Controls the coupon management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java index eddf0df9..3f87b773 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java @@ -1,3 +1,9 @@ +/* + * Controls the customer accounts screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java index a724a367..c95dd62d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java @@ -1,3 +1,9 @@ +/* + * Controls the inventory management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java index 6c3427ea..f7988cf3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java @@ -1,3 +1,9 @@ +/* + * Controls the login screen and handles authentication. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index 11d8cceb..1ba56945 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -1,3 +1,9 @@ +/* + * Controls the main layout with sidebar navigation. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; 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() { new Thread(() -> { 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) { Circle border = new Circle(29); border.setFill(Color.web("#dbe4ee")); @@ -427,6 +443,11 @@ public class MainLayoutController { 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() { UserSession session = UserSession.getInstance(); @@ -498,6 +519,10 @@ public class MainLayoutController { }).start(); } + /** + * Swaps the main content area to a different view. + * @param fxmlFile the FXML filename inside the modelviews folder + */ private void loadView(String fxmlFile) { try { 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) { return switch (orientation) { 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) { try { if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1; 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 35201c45..a0eaf3c5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -1,3 +1,9 @@ +/* + * Controls the pet listings screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java index 6a66c865..b5008eab 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductController.java @@ -1,3 +1,9 @@ +/* + * Controls the product catalog screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.animation.KeyFrame; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java index 8060a68f..7a79ea82 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java @@ -1,3 +1,9 @@ +/* + * Controls the product-supplier linking screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java index ca688856..29d40c89 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java @@ -1,3 +1,9 @@ +/* + * Controls the purchase order management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java index 7e71dc32..dc5dadd3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java @@ -1,3 +1,9 @@ +/* + * Controls the sales and transactions screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.collections.FXCollections; @@ -427,6 +433,10 @@ public class SaleController { 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() { boolean isAdmin = UserSession.getInstance().isAdmin(); vbCreateSale.setVisible(!isAdmin); @@ -440,6 +450,12 @@ public class SaleController { 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) { btnRefresh.setDisable(true); Task> task = new Task>() { @@ -456,6 +472,7 @@ public class SaleController { : ""; 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 saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0; double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0; @@ -816,6 +833,12 @@ public class SaleController { 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) { ObservableList items = FXCollections.observableArrayList(); 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() { BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum()); 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) { if (appliedCoupon == null) { 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) { if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) { return BigDecimal.ZERO; @@ -919,6 +957,13 @@ public class SaleController { 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) { if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) { return 0; @@ -998,6 +1043,10 @@ public class SaleController { 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) { String f = filter == null ? "" : filter.trim().toLowerCase(); String selectedPayment = cbFilterPayment.getValue(); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java index 10acb9ed..ccc7c309 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java @@ -1,3 +1,9 @@ +/* + * Controls the services management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; 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 36c7378b..b8abc026 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -1,3 +1,9 @@ +/* + * Controls the staff accounts screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java index c6120d15..132cd54f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java @@ -1,3 +1,9 @@ +/* + * Controls the supplier management screen. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers; import javafx.application.Platform; 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 39947fcc..59ced6ac 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 @@ -1,3 +1,9 @@ +/* + * Controls the dialog for creating or editing an adoption. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; 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 1b5530cd..a3934850 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 @@ -1,3 +1,9 @@ +/* + * Controls the dialog for creating or editing an appointment. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java index 21fbc521..d477d543 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CouponDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for creating or editing a coupon. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.collections.FXCollections; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java index 92c800e3..ddf4ffcf 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for editing customer details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java index 2aa797e4..1ee51acb 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for adjusting inventory levels. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; 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 ea4e853a..9ae1c11d 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 @@ -1,3 +1,9 @@ +/* + * Controls the dialog for adding or editing a pet. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java index 2a51e42a..8027b863 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for adding or editing a product. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.collections.FXCollections; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductSupplierDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductSupplierDialogController.java index 8e2ef094..b3738276 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductSupplierDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ProductSupplierDialogController.java @@ -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; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PurchaseOrderDetailsDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PurchaseOrderDetailsDialogController.java index ff597d36..b609ba9a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PurchaseOrderDetailsDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/PurchaseOrderDetailsDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog that shows purchase order details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.fxml.FXML; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/RefundDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/RefundDialogController.java index 11a186db..3de67979 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/RefundDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/RefundDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for processing a refund. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.collections.FXCollections; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java index 4774c14d..144ae5e5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SaleDetailDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog that shows sale details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.fxml.FXML; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java index 26f13b0f..b69e008a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for creating or editing a service. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java index 86a8603a..12cd832c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for editing staff member details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java index 04fc74bf..b82bdcf5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for registering a new staff member. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SupplierDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SupplierDialogController.java index 44131a6c..173f1194 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SupplierDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/SupplierDialogController.java @@ -1,3 +1,9 @@ +/* + * Controls the dialog for adding or editing a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.controllers.dialogcontrollers; import javafx.event.EventHandler; -- 2.49.1 From 41fd7a23b94a145a54c7befb87ceaf9ede83dd3c Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 17:06:27 -0600 Subject: [PATCH 40/42] comment desktop api layer --- .../example/petshopdesktop/api/ApiClient.java | 44 +++++++++++++ .../example/petshopdesktop/api/ApiConfig.java | 6 ++ .../api/ChatRealtimeClient.java | 62 +++++++++++++++++++ .../api/dto/activity/ActivityLogResponse.java | 6 ++ .../api/dto/adoption/AdoptionRequest.java | 6 ++ .../api/dto/adoption/AdoptionResponse.java | 6 ++ .../api/dto/analytics/DailySales.java | 6 ++ .../api/dto/analytics/DashboardResponse.java | 6 ++ .../api/dto/analytics/TopProduct.java | 6 ++ .../dto/appointment/AppointmentRequest.java | 6 ++ .../dto/appointment/AppointmentResponse.java | 6 ++ .../api/dto/auth/AvatarUploadResponse.java | 6 ++ .../api/dto/auth/LoginRequest.java | 6 ++ .../api/dto/auth/LoginResponse.java | 6 ++ .../api/dto/auth/UserInfoResponse.java | 6 ++ .../api/dto/chat/ConversationRequest.java | 6 ++ .../api/dto/chat/ConversationResponse.java | 6 ++ .../api/dto/chat/MessageRequest.java | 6 ++ .../api/dto/chat/MessageResponse.java | 6 ++ .../dto/chat/UpdateConversationRequest.java | 6 ++ .../api/dto/common/BulkDeleteRequest.java | 6 ++ .../api/dto/common/DropdownOption.java | 6 ++ .../api/dto/common/PageResponse.java | 6 ++ .../api/dto/coupon/CouponRequest.java | 6 ++ .../api/dto/coupon/CouponResponse.java | 6 ++ .../api/dto/employee/EmployeeRequest.java | 6 ++ .../api/dto/employee/EmployeeResponse.java | 6 ++ .../api/dto/inventory/InventoryRequest.java | 6 ++ .../api/dto/inventory/InventoryResponse.java | 6 ++ .../api/dto/pet/PetRequest.java | 6 ++ .../api/dto/pet/PetResponse.java | 6 ++ .../api/dto/product/ProductRequest.java | 6 ++ .../api/dto/product/ProductResponse.java | 6 ++ .../ProductSupplierRequest.java | 6 ++ .../ProductSupplierResponse.java | 6 ++ .../purchaseorder/PurchaseOrderResponse.java | 6 ++ .../api/dto/sale/SaleItemRequest.java | 6 ++ .../api/dto/sale/SaleItemResponse.java | 6 ++ .../api/dto/sale/SaleRequest.java | 6 ++ .../api/dto/sale/SaleResponse.java | 6 ++ .../api/dto/service/ServiceRequest.java | 6 ++ .../api/dto/service/ServiceResponse.java | 6 ++ .../api/dto/supplier/SupplierRequest.java | 6 ++ .../api/dto/supplier/SupplierResponse.java | 6 ++ .../api/dto/user/UserRequest.java | 6 ++ .../api/dto/user/UserResponse.java | 6 ++ .../api/endpoints/ActivityLogApi.java | 6 ++ .../api/endpoints/AdoptionApi.java | 6 ++ .../api/endpoints/AnalyticsApi.java | 6 ++ .../api/endpoints/AppointmentApi.java | 6 ++ .../petshopdesktop/api/endpoints/AuthApi.java | 6 ++ .../petshopdesktop/api/endpoints/ChatApi.java | 6 ++ .../api/endpoints/CouponApi.java | 6 ++ .../api/endpoints/CustomerApi.java | 6 ++ .../api/endpoints/DropdownApi.java | 6 ++ .../api/endpoints/EmployeeApi.java | 6 ++ .../api/endpoints/InventoryApi.java | 6 ++ .../petshopdesktop/api/endpoints/PetApi.java | 6 ++ .../api/endpoints/ProductApi.java | 6 ++ .../api/endpoints/ProductSupplierApi.java | 6 ++ .../api/endpoints/PurchaseOrderApi.java | 6 ++ .../petshopdesktop/api/endpoints/SaleApi.java | 6 ++ .../api/endpoints/ServiceApi.java | 6 ++ .../api/endpoints/SupplierApi.java | 6 ++ .../petshopdesktop/api/endpoints/UserApi.java | 6 ++ 65 files changed, 484 insertions(+) diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java b/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java index baf1b99a..7317709c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ApiClient.java @@ -1,3 +1,9 @@ +/* + * Handles sending HTTP requests to the backend server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api; import com.fasterxml.jackson.databind.ObjectMapper; @@ -116,6 +122,15 @@ public class ApiClient { return handleResponse(response, responseClass); } + /** + * Uploads a single file as a multipart/form-data POST. + * Manually builds the multipart body since Java's HttpClient doesn't have built-in support. + * @param path API endpoint path + * @param partName the form field name for the file + * @param filePath local file to upload + * @param responseClass type to deserialize the response into + * @return the parsed response + */ public T postMultipart(String path, String partName, Path filePath, Class responseClass) throws Exception { String boundary = "----PetShopDesktop" + UUID.randomUUID(); String mimeType = Files.probeContentType(filePath); @@ -146,6 +161,17 @@ public class ApiClient { return handleResponse(response, responseClass); } + /** + * Uploads a file along with an optional text field in a single multipart POST. + * Uses SequenceInputStream to stream the file instead of loading everything into one byte array. + * @param path API endpoint path + * @param filePartName form field name for the file + * @param filePath local file to upload + * @param textPartName form field name for the text + * @param textContent the text value to send alongside the file + * @param responseClass type to deserialize the response into + * @return the parsed response + */ public T postMultipartWithText(String path, String filePartName, Path filePath, String textPartName, String textContent, Class responseClass) throws Exception { @@ -218,6 +244,11 @@ public class ApiClient { } } + /** + * Sends a DELETE with a JSON body (used for batch deletes). + * @param path API endpoint path + * @param requestBody object to serialize as the request body + */ public void deleteWithBody(String path, Object requestBody) throws Exception { String jsonBody = objectMapper.writeValueAsString(requestBody); @@ -244,6 +275,12 @@ public class ApiClient { } } + /** + * Checks the HTTP status and either deserializes the body or throws a readable error. + * @param response the raw HTTP response + * @param responseClass type to deserialize into + * @return the parsed response body, or null for 204/empty + */ private T handleResponse(HttpResponse response, Class responseClass) throws Exception { int statusCode = response.statusCode(); @@ -263,6 +300,13 @@ public class ApiClient { } } + /** + * Tries to extract a human-readable error from the JSON response body. + * Looks for an "errors" map first (field validation), then a "message" field, + * and falls back to the raw status code. + * @param response the failed HTTP response + * @return a user-facing error string + */ private String parseErrorMessage(HttpResponse response) { try { if (response.body() != null && !response.body().isEmpty()) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ApiConfig.java b/desktop/src/main/java/org/example/petshopdesktop/api/ApiConfig.java index 3394c653..8e6fd90d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ApiConfig.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ApiConfig.java @@ -1,3 +1,9 @@ +/* + * Stores the base URL and connection settings for the API. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api; import java.io.IOException; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java index a505c514..1042a0f3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java @@ -1,3 +1,9 @@ +/* + * Manages a real-time WebSocket connection for the chat feature. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api; import org.example.petshopdesktop.api.dto.chat.ConversationResponse; @@ -59,6 +65,10 @@ public class ChatRealtimeClient implements WebSocket.Listener { return INSTANCE; } + /** + * Registers a listener that gets called when chat notification state changes. + * @param listener receives true if there are chats needing attention + */ public void addNotificationListener(Consumer listener) { synchronized (lock) { notificationListeners.add(listener); @@ -66,6 +76,11 @@ public class ChatRealtimeClient implements WebSocket.Listener { } } + /** + * Seeds the global conversation map from a full list fetch. + * Called on first load and on refresh to sync local state with server. + * @param conversations the full list from the REST API + */ public void initializeState(List conversations) { synchronized (lock) { globalConversations.clear(); @@ -76,6 +91,12 @@ public class ChatRealtimeClient implements WebSocket.Listener { updateNotificationState(); } + /** + * Marks a conversation as replied to by updating the last sender and read state. + * Used after the current user sends a message so the badge clears. + * @param conversationId the conversation that was replied to + * @param senderId the user who sent the reply + */ public void markConversationReplied(Long conversationId, Long senderId) { synchronized (lock) { ConversationResponse conv = globalConversations.get(conversationId); @@ -100,6 +121,12 @@ public class ChatRealtimeClient implements WebSocket.Listener { } } + /** + * Checks if any open conversation needs staff attention. + * Two cases count: unassigned chats waiting for pickup, + * and chats assigned to me where the customer sent the last message. + * @return true if there's at least one chat needing action + */ public boolean hasActionableChats() { synchronized (lock) { UserSession session = UserSession.getInstance(); @@ -125,6 +152,9 @@ public class ChatRealtimeClient implements WebSocket.Listener { } } + /** + * Recalculates the badge state and notifies listeners only if it changed. + */ private void updateNotificationState() { boolean currentState = hasActionableChats(); List> listeners; @@ -153,6 +183,10 @@ public class ChatRealtimeClient implements WebSocket.Listener { } } + /** + * Opens the WebSocket and sends a STOMP CONNECT frame with the JWT. + * No-ops if already connected or mid-handshake. + */ public void connect() { String token = UserSession.getInstance().getJwtToken(); if (token == null || token.isBlank()) { @@ -167,6 +201,7 @@ public class ChatRealtimeClient implements WebSocket.Listener { connecting = true; } + // Convert the REST base URL to a WebSocket URL String wsUrl = ApiConfig.getInstance().getBaseUrl() .replaceFirst("^http://", "ws://") .replaceFirst("^https://", "wss://") + "/ws/chat"; @@ -236,6 +271,13 @@ public class ChatRealtimeClient implements WebSocket.Listener { } } + /** + * Sends a chat message over the WebSocket using a STOMP SEND frame. + * Falls back to reconnecting if the socket isn't ready. + * @param conversationId target conversation + * @param content the message text + * @return true if the frame was sent, false if not connected + */ public boolean sendMessage(Long conversationId, String content) { String token = UserSession.getInstance().getJwtToken(); if (token == null || token.isBlank()) { @@ -287,6 +329,10 @@ public class ChatRealtimeClient implements WebSocket.Listener { applySelectedConversationSubscriptionLocked(); } + /** + * Subscribes to the currently selected conversation's message topic. + * Unsubscribes from the previous one first if it changed. + */ private void applySelectedConversationSubscriptionLocked() { if (webSocket == null || !connected) { return; @@ -324,6 +370,12 @@ public class ChatRealtimeClient implements WebSocket.Listener { updateNotificationState(); } + /** + * Parses a single STOMP frame and dispatches it. + * Handles CONNECTED (finish handshake), MESSAGE (deliver to listeners), + * and ERROR frames. + * @param frame the raw STOMP frame text without the null terminator + */ private void handleFrame(String frame) { String normalized = frame.replace("\r\n", "\n"); int separator = normalized.indexOf("\n\n"); @@ -354,11 +406,14 @@ public class ChatRealtimeClient implements WebSocket.Listener { } if ("MESSAGE".equals(command)) { + // Look up which topic this subscription maps to String destination; synchronized (lock) { destination = destinationBySubscription.get(headers.get("subscription")); } try { + // Messages on a specific conversation topic are chat messages; + // messages on the general conversations topic are conversation updates if (destination != null && destination.startsWith("/topic/chat/conversations/")) { MessageResponse message = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, MessageResponse.class); if (messageListener != null) { @@ -371,12 +426,14 @@ public class ChatRealtimeClient implements WebSocket.Listener { .notifyNewMessage(message.getSenderDisplayName(), message.getContent()); } + // Keep the global map in sync for badge calculations synchronized (lock) { ConversationResponse conv = globalConversations.get(message.getConversationId()); if (conv != null) { conv.setLastMessage(message.getContent()); conv.setLastSenderId(message.getSenderId()); } + // Incoming message from someone else marks it unread again if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) { readConversationIds.remove(message.getConversationId()); } @@ -416,6 +473,11 @@ public class ChatRealtimeClient implements WebSocket.Listener { } @Override + /** + * Accumulates WebSocket text fragments and splits on the STOMP null delimiter. + * A single WebSocket message can contain partial frames, so we buffer until + * we see the \0 that marks the end of a STOMP frame. + */ public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { synchronized (lock) { frameBuffer.append(data); diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java index 482476eb..8930c012 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for an activity log entry. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.activity; import java.time.LocalDateTime; 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 b1d56eab..2df613b0 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 @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating an adoption. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.adoption; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java index 56134bff..7e3e6469 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for an adoption record. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.adoption; import java.time.LocalDate; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DailySales.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DailySales.java index 320a8266..4647a4ca 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DailySales.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DailySales.java @@ -1,3 +1,9 @@ +/* + * Holds daily sales total data returned from the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.analytics; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DashboardResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DashboardResponse.java index 38b17b81..bd5cc464 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DashboardResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/DashboardResponse.java @@ -1,3 +1,9 @@ +/* + * Holds all the summary data shown on the dashboard. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.analytics; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/TopProduct.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/TopProduct.java index a62ccce9..d6b82da8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/TopProduct.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/analytics/TopProduct.java @@ -1,3 +1,9 @@ +/* + * Holds data for a top-selling product from the analytics endpoint. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.analytics; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java index e8166411..3d610486 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating an appointment. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.appointment; import java.time.LocalDate; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java index dd74554b..84c5dfc9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for an appointment. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.appointment; import java.time.LocalDate; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/AvatarUploadResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/AvatarUploadResponse.java index 24dadcca..7eae590c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/AvatarUploadResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/AvatarUploadResponse.java @@ -1,3 +1,9 @@ +/* + * Holds the URL returned after uploading a user avatar. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.auth; public class AvatarUploadResponse { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginRequest.java index 89d6a98f..57e03da5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginRequest.java @@ -1,3 +1,9 @@ +/* + * Holds the username and password sent when logging in. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.auth; public class LoginRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginResponse.java index 50354d48..abb0e6f7 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/LoginResponse.java @@ -1,3 +1,9 @@ +/* + * Holds the token and user info returned after logging in. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.auth; public class LoginResponse { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java index fe83893c..0a62318b 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java @@ -1,3 +1,9 @@ +/* + * Holds the current user's profile information from the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.auth; public class UserInfoResponse { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationRequest.java index cd5ef6ee..a5946bad 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when starting a new conversation. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.chat; public class ConversationRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationResponse.java index 30a24fbb..ee0887de 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/ConversationResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a conversation. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.chat; import java.time.LocalDateTime; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java index a5c17ca4..9adfe551 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when sending a chat message. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.chat; public class MessageRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java index 7aecada0..b8026f96 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a chat message. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.chat; import java.time.LocalDateTime; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/UpdateConversationRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/UpdateConversationRequest.java index 3bacbca9..17cd18fa 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/UpdateConversationRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/UpdateConversationRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when updating a conversation. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.chat; public class UpdateConversationRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/BulkDeleteRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/BulkDeleteRequest.java index d95798ef..cc5f29b3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/BulkDeleteRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/BulkDeleteRequest.java @@ -1,3 +1,9 @@ +/* + * Holds a list of IDs to delete multiple records at once. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.common; import java.util.List; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/DropdownOption.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/DropdownOption.java index bacbad0b..d453fe0e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/DropdownOption.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/DropdownOption.java @@ -1,3 +1,9 @@ +/* + * Holds an ID and label pair used to fill dropdown menus. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.common; public class DropdownOption { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/PageResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/PageResponse.java index 5a27e2e9..9c4b8df1 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/PageResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/common/PageResponse.java @@ -1,3 +1,9 @@ +/* + * Holds a page of results along with pagination details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.common; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java index dc11d55c..f8708c5d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a coupon. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.coupon; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java index e1c23f0d..0acf2698 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/coupon/CouponResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a coupon. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.coupon; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java index dd2a7271..9d4a0c29 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating an employee. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.employee; public class EmployeeRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java index c6a32d23..a3a59e63 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for an employee. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.employee; import java.time.LocalDateTime; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java index 935a713e..4182b455 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when updating inventory. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.inventory; public class InventoryRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java index 176228b9..536d058a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for an inventory record. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.inventory; import java.time.LocalDateTime; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetRequest.java index 4bb56185..c6e793dc 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a pet. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.pet; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetResponse.java index f0961a18..d850111c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/pet/PetResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a pet. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.pet; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductRequest.java index 020afca6..9e4c4461 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a product. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.product; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductResponse.java index 18f9b678..65901061 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/product/ProductResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a product. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.product; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierRequest.java index 69f4ad61..ee862426 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when linking a product to a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.productsupplier; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierResponse.java index b601efe3..cb31d536 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/productsupplier/ProductSupplierResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a product-supplier link. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.productsupplier; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/purchaseorder/PurchaseOrderResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/purchaseorder/PurchaseOrderResponse.java index c928e649..a8aeb8a5 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/purchaseorder/PurchaseOrderResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/purchaseorder/PurchaseOrderResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a purchase order. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.purchaseorder; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemRequest.java index 03401958..3e5de272 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data for a single item in a sale request. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.sale; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemResponse.java index 8bed99b1..759a7f26 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleItemResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a single sale item. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.sale; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleRequest.java index fe324bb5..fe9ff428 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating a sale. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.sale; import java.util.List; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleResponse.java index f814cc17..aaf4facf 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/sale/SaleResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a sale. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.sale; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceRequest.java index 8fd68a1b..2e68d972 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a service. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.service; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceResponse.java index caf2e3ff..374fc1fc 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/service/ServiceResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a service. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.service; import java.math.BigDecimal; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierRequest.java index fb3dcd92..d437877f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.supplier; public class SupplierRequest { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierResponse.java index 64db982f..7325b69e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/supplier/SupplierResponse.java @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a supplier. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.supplier; public class SupplierResponse { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java index e4dd940d..080ed678 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java @@ -1,3 +1,9 @@ +/* + * Holds data sent to the server when creating or updating a user. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.user; public class UserRequest { 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 2e1daa0e..3df18650 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 @@ -1,3 +1,9 @@ +/* + * Holds data returned from the server for a user. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.dto.user; import java.time.LocalDateTime; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java index 6c092b78..2bcf5c64 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to fetch activity logs from the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AdoptionApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AdoptionApi.java index e9291e8f..a46cbad6 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AdoptionApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AdoptionApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage adoptions through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AnalyticsApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AnalyticsApi.java index ebecd9c2..975d8725 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AnalyticsApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AnalyticsApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to fetch analytics and dashboard data from the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import org.example.petshopdesktop.api.ApiClient; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java index 0fe56661..25381423 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage appointments through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java index e45ead94..476d213c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods for logging in and managing authentication. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import org.example.petshopdesktop.api.ApiClient; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ChatApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ChatApi.java index 3d58547e..f40a5998 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ChatApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ChatApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage chat conversations and messages. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java index a08e0016..652be9f3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CouponApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage coupons through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CustomerApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CustomerApi.java index 54f89f66..0abd05e4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CustomerApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/CustomerApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage customers through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/DropdownApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/DropdownApi.java index 4f0d34b9..1ef3dcf8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/DropdownApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/DropdownApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to fetch dropdown option lists from the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java index e3a8ad52..7505cc87 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage employees through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/InventoryApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/InventoryApi.java index bf34cf22..0660c816 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/InventoryApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/InventoryApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage inventory through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java index 865bb1ea..79221563 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage pets through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java index c5ec100d..d3a61f5f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage products through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductSupplierApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductSupplierApi.java index 74143c0f..4f95923a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductSupplierApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ProductSupplierApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage product-supplier links through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PurchaseOrderApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PurchaseOrderApi.java index 5ce083f9..5465ed64 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PurchaseOrderApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PurchaseOrderApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage purchase orders through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SaleApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SaleApi.java index ab1c4fa1..bb7b704b 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SaleApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SaleApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage sales through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ServiceApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ServiceApi.java index 66b348dc..91008473 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ServiceApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ServiceApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage services through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SupplierApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SupplierApi.java index bcfb8acb..e8f3ea42 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SupplierApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/SupplierApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage suppliers through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java index 27439ee9..0d2da6d4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/UserApi.java @@ -1,3 +1,9 @@ +/* + * Provides methods to manage user accounts through the server. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.api.endpoints; import com.fasterxml.jackson.core.type.TypeReference; -- 2.49.1 From 22981606503cde4f81bd6ddc10756b11d61343f8 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 20 Apr 2026 17:51:03 -0600 Subject: [PATCH 41/42] comment desktop models and utils --- desktop/src/main/java/module-info.java | 6 +++++ .../petshopdesktop/DTOs/AppointmentDTO.java | 6 +++++ .../petshopdesktop/DTOs/ProductDTO.java | 6 +++++ .../DTOs/ProductSupplierDTO.java | 6 +++++ .../petshopdesktop/DTOs/PurchaseOrderDTO.java | 6 +++++ .../example/petshopdesktop/DTOs/SaleDTO.java | 6 +++++ .../petshopdesktop/DTOs/ServiceDTO.java | 8 ++++++- .../org/example/petshopdesktop/Launcher.java | 6 +++++ .../petshopdesktop/PetShopApplication.java | 6 +++++ .../org/example/petshopdesktop/Validator.java | 6 +++++ .../org/example/petshopdesktop/auth/Role.java | 6 +++++ .../petshopdesktop/auth/UserSession.java | 6 +++++ .../petshopdesktop/models/Adoption.java | 6 +++++ .../petshopdesktop/models/Appointment.java | 8 ++++++- .../petshopdesktop/models/Category.java | 6 +++++ .../petshopdesktop/models/Customer.java | 6 +++++ .../petshopdesktop/models/Inventory.java | 6 +++++ .../example/petshopdesktop/models/Pet.java | 6 +++++ .../petshopdesktop/models/Product.java | 6 +++++ .../models/ProductSupplier.java | 6 +++++ .../petshopdesktop/models/PurchaseOrder.java | 8 ++++++- .../petshopdesktop/models/SaleCartItem.java | 6 +++++ .../petshopdesktop/models/SaleDetail.java | 6 +++++ .../petshopdesktop/models/SaleLineItem.java | 6 +++++ .../petshopdesktop/models/Service.java | 8 ++++++- .../petshopdesktop/models/StaffAccount.java | 6 +++++ .../petshopdesktop/models/Supplier.java | 6 +++++ .../example/petshopdesktop/models/User.java | 6 +++++ .../models/analytics/DailySalesData.java | 6 +++++ .../models/analytics/EmployeeSalesData.java | 6 +++++ .../models/analytics/PaymentMethodData.java | 6 +++++ .../models/analytics/ProductSalesData.java | 6 +++++ .../models/analytics/SalesSummary.java | 6 +++++ .../petshopdesktop/ui/CalendarPane.java | 23 +++++++++++++++++++ .../petshopdesktop/ui/SvgWebViewFactory.java | 6 +++++ .../petshopdesktop/util/ActivityLogger.java | 6 +++++ .../util/DesktopImageSupport.java | 6 +++++ .../util/DesktopNotificationService.java | 6 +++++ .../util/FilePickerSupport.java | 6 +++++ .../petshopdesktop/util/TableViewSupport.java | 21 +++++++++++++++++ .../util/TextFieldFormatSupport.java | 6 +++++ 41 files changed, 282 insertions(+), 4 deletions(-) diff --git a/desktop/src/main/java/module-info.java b/desktop/src/main/java/module-info.java index 93a6704e..1a504108 100644 --- a/desktop/src/main/java/module-info.java +++ b/desktop/src/main/java/module-info.java @@ -1,3 +1,9 @@ +/* + * Module descriptor for the pet shop desktop application. + * + * Author: Harkamal + * Date: April 2026 + */ module org.example.petshopdesktop { requires javafx.controls; requires javafx.fxml; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java index 82b242b9..077d37df 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for appointment data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.SimpleIntegerProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java index 3270d6ca..425403bc 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for product data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductSupplierDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductSupplierDTO.java index 8fb481b0..9e39a8fa 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductSupplierDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ProductSupplierDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for product-supplier data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java index d30053cb..c12dd359 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for purchase order data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.*; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java index a20bf5fd..37f91e2d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for sale data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.*; diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java index c876cd3f..2b857bcd 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java @@ -1,3 +1,9 @@ +/* + * Table-friendly wrapper for service data using JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.DTOs; import javafx.beans.property.SimpleDoubleProperty; @@ -107,4 +113,4 @@ public class ServiceDTO { return service; } -} \ No newline at end of file +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/Launcher.java b/desktop/src/main/java/org/example/petshopdesktop/Launcher.java index 70456482..02c3c863 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/Launcher.java +++ b/desktop/src/main/java/org/example/petshopdesktop/Launcher.java @@ -1,3 +1,9 @@ +/* + * Entry point that starts the JavaFX application. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop; import javafx.application.Application; diff --git a/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java b/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java index 18296405..24bf8718 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java +++ b/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java @@ -1,3 +1,9 @@ +/* + * Main JavaFX application that sets up and launches the window. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop; import javafx.application.Application; diff --git a/desktop/src/main/java/org/example/petshopdesktop/Validator.java b/desktop/src/main/java/org/example/petshopdesktop/Validator.java index 1a453556..5b473da1 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/Validator.java +++ b/desktop/src/main/java/org/example/petshopdesktop/Validator.java @@ -1,3 +1,9 @@ +/* + * Shared input validation methods used across the app. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop; public class Validator { diff --git a/desktop/src/main/java/org/example/petshopdesktop/auth/Role.java b/desktop/src/main/java/org/example/petshopdesktop/auth/Role.java index 64f38459..3c9a1042 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/auth/Role.java +++ b/desktop/src/main/java/org/example/petshopdesktop/auth/Role.java @@ -1,3 +1,9 @@ +/* + * Defines the roles a user can have in the system. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.auth; public enum Role { diff --git a/desktop/src/main/java/org/example/petshopdesktop/auth/UserSession.java b/desktop/src/main/java/org/example/petshopdesktop/auth/UserSession.java index cd5646f4..095c7ae8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/auth/UserSession.java +++ b/desktop/src/main/java/org/example/petshopdesktop/auth/UserSession.java @@ -1,3 +1,9 @@ +/* + * Stores the currently logged-in user's session details. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.auth; public class UserSession { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java b/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java index 73f23afc..5575a26d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java @@ -1,3 +1,9 @@ +/* + * Represents an adoption record with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Appointment.java b/desktop/src/main/java/org/example/petshopdesktop/models/Appointment.java index 7ab54292..c3181a40 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Appointment.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Appointment.java @@ -1,3 +1,9 @@ +/* + * Represents an appointment with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleIntegerProperty; @@ -43,4 +49,4 @@ public class Appointment { public SimpleStringProperty appointmentDateProperty() { return appointmentDate; } public SimpleStringProperty appointmentTimeProperty() { return appointmentTime; } public SimpleStringProperty appointmentStatusProperty() { return appointmentStatus; } -} \ No newline at end of file +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Category.java b/desktop/src/main/java/org/example/petshopdesktop/models/Category.java index 1f28182b..647ff623 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Category.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Category.java @@ -1,3 +1,9 @@ +/* + * Represents a product category with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleIntegerProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Customer.java b/desktop/src/main/java/org/example/petshopdesktop/models/Customer.java index 66602694..5103e0d3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Customer.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Customer.java @@ -1,3 +1,9 @@ +/* + * Represents a customer with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleIntegerProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Inventory.java b/desktop/src/main/java/org/example/petshopdesktop/models/Inventory.java index 53f77543..85d3943d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Inventory.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Inventory.java @@ -1,3 +1,9 @@ +/* + * Represents an inventory entry with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleIntegerProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java b/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java index bc7e1a5c..bf983c36 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Pet.java @@ -1,3 +1,9 @@ +/* + * Represents a pet with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Product.java b/desktop/src/main/java/org/example/petshopdesktop/models/Product.java index bf0913e5..dd5f96cb 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Product.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Product.java @@ -1,3 +1,9 @@ +/* + * Represents a product with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/ProductSupplier.java b/desktop/src/main/java/org/example/petshopdesktop/models/ProductSupplier.java index e674974d..d3603f0d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/ProductSupplier.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/ProductSupplier.java @@ -1,3 +1,9 @@ +/* + * Represents a product-supplier relationship with JavaFX properties. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleDoubleProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java b/desktop/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java index c13afe50..d2def56f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java @@ -1,3 +1,9 @@ +/* + * Represents a purchase order with fields for display in tables. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import java.math.BigDecimal; @@ -49,4 +55,4 @@ public class PurchaseOrder { public BigDecimal getTotalAmount() { return totalAmount; } -} \ No newline at end of file +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/SaleCartItem.java b/desktop/src/main/java/org/example/petshopdesktop/models/SaleCartItem.java index 25c4da36..abd4e764 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/SaleCartItem.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/SaleCartItem.java @@ -1,3 +1,9 @@ +/* + * Represents a single item added to the sale cart. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; public class SaleCartItem { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java b/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java index 63d3cf47..875a04c7 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/SaleDetail.java @@ -1,3 +1,9 @@ +/* + * Holds the full details of a completed sale for display. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.collections.ObservableList; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/SaleLineItem.java b/desktop/src/main/java/org/example/petshopdesktop/models/SaleLineItem.java index f45938b0..014ced54 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/SaleLineItem.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/SaleLineItem.java @@ -1,3 +1,9 @@ +/* + * Represents one line item within a sale. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; public class SaleLineItem { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Service.java b/desktop/src/main/java/org/example/petshopdesktop/models/Service.java index b259fc21..5c5c4265 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Service.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Service.java @@ -1,3 +1,9 @@ +/* + * Represents a service offered by the pet shop. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleDoubleProperty; @@ -95,4 +101,4 @@ public class Service { public String toString() { return getServiceName(); } -} \ No newline at end of file +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/StaffAccount.java b/desktop/src/main/java/org/example/petshopdesktop/models/StaffAccount.java index 59923338..fca3aeb2 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/StaffAccount.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/StaffAccount.java @@ -1,3 +1,9 @@ +/* + * Represents a staff account used for login and permissions. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import java.sql.Timestamp; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Supplier.java b/desktop/src/main/java/org/example/petshopdesktop/models/Supplier.java index 1120670a..7e0386f4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Supplier.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Supplier.java @@ -1,3 +1,9 @@ +/* + * Represents a supplier with JavaFX properties for table binding. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import javafx.beans.property.SimpleIntegerProperty; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/User.java b/desktop/src/main/java/org/example/petshopdesktop/models/User.java index 7d2199e9..d006d55f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/User.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/User.java @@ -1,3 +1,9 @@ +/* + * Represents the currently logged-in user. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models; import org.example.petshopdesktop.auth.Role; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/DailySalesData.java b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/DailySalesData.java index 1f41b8f2..d4e2d572 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/DailySalesData.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/DailySalesData.java @@ -1,3 +1,9 @@ +/* + * Holds sales data for a single day used in charts. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models.analytics; import java.time.LocalDate; diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/EmployeeSalesData.java b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/EmployeeSalesData.java index a954d86b..94143f3e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/EmployeeSalesData.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/EmployeeSalesData.java @@ -1,3 +1,9 @@ +/* + * Holds sales data grouped by employee for charts. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models.analytics; public class EmployeeSalesData { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/PaymentMethodData.java b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/PaymentMethodData.java index de45c727..b3ba30a8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/PaymentMethodData.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/PaymentMethodData.java @@ -1,3 +1,9 @@ +/* + * Holds sales data grouped by payment method for charts. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models.analytics; public class PaymentMethodData { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/ProductSalesData.java b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/ProductSalesData.java index 3723eee6..2a802bca 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/ProductSalesData.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/ProductSalesData.java @@ -1,3 +1,9 @@ +/* + * Holds sales data grouped by product for charts. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models.analytics; public class ProductSalesData { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/SalesSummary.java b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/SalesSummary.java index 65e9f9c1..ca06fd18 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/analytics/SalesSummary.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/analytics/SalesSummary.java @@ -1,3 +1,9 @@ +/* + * Holds overall sales totals for the analytics dashboard. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.models.analytics; public class SalesSummary { diff --git a/desktop/src/main/java/org/example/petshopdesktop/ui/CalendarPane.java b/desktop/src/main/java/org/example/petshopdesktop/ui/CalendarPane.java index 2b2afeab..321c8a03 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/ui/CalendarPane.java +++ b/desktop/src/main/java/org/example/petshopdesktop/ui/CalendarPane.java @@ -1,3 +1,9 @@ +/* + * Custom calendar widget for picking and highlighting dates. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.ui; import javafx.geometry.Insets; @@ -87,6 +93,10 @@ public class CalendarPane extends VBox { onDateSelected = handler; } + /** + * Moves the calendar forward or backward by one week or month depending on mode. + * @param direction +1 for forward, -1 for backward + */ private void navigate(int direction) { if (weekMode) { currentDate = currentDate.plusWeeks(direction); @@ -96,6 +106,11 @@ public class CalendarPane extends VBox { rebuild(); } + /** + * Rebuilds the day grid from scratch. In week mode, shows 7 days starting + * from Sunday. In month mode, calculates how many rows are needed and offsets + * the first day to the correct column based on its day-of-week. + */ private void rebuild() { dayGrid.getChildren().clear(); dayGrid.getColumnConstraints().clear(); @@ -108,6 +123,7 @@ public class CalendarPane extends VBox { } if (weekMode) { + // getValue() gives Mon=1..Sun=7; mod 7 maps to Sun=0..Sat=6 int dow = currentDate.getDayOfWeek().getValue() % 7; LocalDate startOfWeek = currentDate.minusDays(dow); lblHeader.setText("Week of " + startOfWeek.format(DateTimeFormatter.ofPattern("MMM d, yyyy"))); @@ -117,6 +133,7 @@ public class CalendarPane extends VBox { } else { YearMonth ym = YearMonth.of(currentDate.getYear(), currentDate.getMonthValue()); lblHeader.setText(currentDate.format(DateTimeFormatter.ofPattern("MMMM yyyy"))); + // Figure out which column the 1st falls on, then how many rows we need int firstDow = ym.atDay(1).getDayOfWeek().getValue() % 7; int daysInMonth = ym.lengthOfMonth(); int rows = (int) Math.ceil((firstDow + daysInMonth) / 7.0); @@ -136,6 +153,12 @@ public class CalendarPane extends VBox { } } + /** + * Creates a single day cell with appropriate styling for selected/today/normal states. + * Shows a red dot if the date has events. Clicking toggles selection. + * @param date the date this cell represents + * @return the styled cell + */ private StackPane buildDayCell(LocalDate date) { boolean isSelected = date.equals(selectedDate); boolean isToday = date.equals(LocalDate.now()); diff --git a/desktop/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java b/desktop/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java index 6243e901..9463df17 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java +++ b/desktop/src/main/java/org/example/petshopdesktop/ui/SvgWebViewFactory.java @@ -1,3 +1,9 @@ +/* + * Renders SVG files inside a WebView for display in the UI. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.ui; import javafx.scene.layout.StackPane; diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/ActivityLogger.java b/desktop/src/main/java/org/example/petshopdesktop/util/ActivityLogger.java index 6a104a39..09d34ac2 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/ActivityLogger.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/ActivityLogger.java @@ -1,3 +1,9 @@ +/* + * Writes user actions to a local log file. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import java.nio.charset.StandardCharsets; diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java index dba81e76..ae4af636 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopImageSupport.java @@ -1,3 +1,9 @@ +/* + * Loads and caches images from the server for display. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import javafx.application.Platform; diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopNotificationService.java b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopNotificationService.java index 4f9da43f..b936e996 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/DesktopNotificationService.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/DesktopNotificationService.java @@ -1,3 +1,9 @@ +/* + * Sends system tray notifications from the desktop app. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import javafx.embed.swing.SwingFXUtils; diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/FilePickerSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/FilePickerSupport.java index b81c764a..d3584478 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/FilePickerSupport.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/FilePickerSupport.java @@ -1,3 +1,9 @@ +/* + * Opens native file chooser dialogs for selecting files. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import javafx.stage.FileChooser; diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/TableViewSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/TableViewSupport.java index addf56ce..bb32fb3a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/TableViewSupport.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/TableViewSupport.java @@ -1,3 +1,9 @@ +/* + * Helper methods for setting up and formatting table views. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import javafx.collections.transformation.FilteredList; @@ -20,6 +26,12 @@ public final class TableViewSupport { private TableViewSupport() { } + /** + * Wraps a FilteredList in a SortedList and binds it to the table's sort order. + * This way clicking column headers sorts the already-filtered data. + * @param tableView the table to bind to + * @param filteredItems the filtered data source + */ public static void bindSortedItems(TableView tableView, FilteredList filteredItems) { SortedList sortedItems = new SortedList<>(filteredItems); sortedItems.comparatorProperty().bind(tableView.comparatorProperty()); @@ -43,6 +55,10 @@ public final class TableViewSupport { delay.playFromStart(); } + /** + * Formats a numeric column as Canadian currency (e.g. $12.50). + * @param column the table column to apply formatting to + */ public static void applyCurrencyColumn(TableColumn column) { if (column == null) { return; @@ -58,6 +74,11 @@ public final class TableViewSupport { }); } + /** + * Adds a double-click handler to table rows that calls the given action with the row item. + * @param tableView the table to add the handler to + * @param action callback that receives the clicked row's item + */ public static void installDoubleClickAction(TableView tableView, Consumer action) { tableView.setRowFactory(tv -> { TableRow row = new TableRow<>(); diff --git a/desktop/src/main/java/org/example/petshopdesktop/util/TextFieldFormatSupport.java b/desktop/src/main/java/org/example/petshopdesktop/util/TextFieldFormatSupport.java index 7dba0d9f..0e86a0e9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/util/TextFieldFormatSupport.java +++ b/desktop/src/main/java/org/example/petshopdesktop/util/TextFieldFormatSupport.java @@ -1,3 +1,9 @@ +/* + * Applies input formatting like phone numbers to text fields. + * + * Author: Harkamal + * Date: April 2026 + */ package org.example.petshopdesktop.util; import javafx.application.Platform; -- 2.49.1 From 6d593726a5b9816aa37ae394f321fbc06e536e12 Mon Sep 17 00:00:00 2001 From: augmentedpotato Date: Mon, 20 Apr 2026 19:19:30 -0600 Subject: [PATCH 42/42] Comments, appointments adjustments, fixed some issues --- .../resources/db/migration/V2__seed_data.sql | 1 + web/app/about/page.js | 2 + web/app/adopt/[id]/page.js | 2 + web/app/adopt/page.js | 10 + web/app/ai-chat/page.js | 13 + web/app/api/[...path]/route.js | 4 + web/app/appointments/page.js | 321 +++++++++++++----- web/app/cart/page.js | 16 +- web/app/chat/page.js | 11 + web/app/contact/page.js | 4 + web/app/forgot-password/page.js | 2 + web/app/layout.js | 3 + web/app/login/page.js | 4 + web/app/page.js | 4 + web/app/products/[id]/page.js | 1 + web/app/products/page.js | 3 + web/app/profile/page.js | 16 +- web/app/register/page.js | 4 + web/app/reset-password/page.js | 2 + web/components/ClientProviders.js | 1 + web/components/FloatingChat.js | 7 +- web/components/Footer.js | 2 + web/components/Navigation.js | 6 + web/components/PetCard.js | 1 + web/components/PetProfile.js | 9 +- web/components/ProductCard.js | 3 + web/components/ProductProfile.js | 9 +- web/components/petUtils.js | 2 + web/context/AuthContext.js | 11 + web/context/CartContext.js | 9 + web/context/ChatWidgetContext.js | 17 +- web/lib/cartApi.js | 3 + web/lib/chatSocket.js | 2 + web/lib/fetchAllPages.js | 1 + 34 files changed, 402 insertions(+), 104 deletions(-) diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 88d3835b..d86626a7 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -199,6 +199,7 @@ INSERT INTO service_species (serviceId, species) VALUES (1, 'Dog'), (1, 'Cat'), (1, 'Rabbit'), +(1, 'Bird'), (2, 'Dog'), (2, 'Cat'), (2, 'Rabbit'), diff --git a/web/app/about/page.js b/web/app/about/page.js index b15ad5e6..7695e81d 100644 --- a/web/app/about/page.js +++ b/web/app/about/page.js @@ -1,3 +1,5 @@ +//About page +//Three info cards covering what the store does, its focus, and how to visit export default function AboutPage() { return (
diff --git a/web/app/adopt/[id]/page.js b/web/app/adopt/[id]/page.js index 9a1ae6d3..ad31e4c5 100644 --- a/web/app/adopt/[id]/page.js +++ b/web/app/adopt/[id]/page.js @@ -7,6 +7,8 @@ import PetProfile from "@/components/PetProfile"; const API_BASE = ""; +//Pet detail page +//Fetches a single pet by ID and passes it to PetProfile export default function PetDetailPage() { const { id } = useParams(); const [pet, setPet] = useState(null); diff --git a/web/app/adopt/page.js b/web/app/adopt/page.js index 7cc23723..472c9d90 100644 --- a/web/app/adopt/page.js +++ b/web/app/adopt/page.js @@ -5,6 +5,8 @@ import PetCard from "@/components/PetCard"; import { fetchAllPages } from "@/lib/fetchAllPages"; import { useCart } from "@/context/CartContext"; +//Adopt page +//Browse available pets with species/breed filters and search const API_BASE = ""; const PAGE_SIZE = 10000; @@ -12,6 +14,8 @@ const inputCls = "px-4 py-[0.6rem] border-2 border-[#ddd] rounded-md text-base o const btnPrimaryCls = "px-[1.4rem] py-[0.6rem] bg-[#e68672] text-white border-none rounded-md text-base cursor-pointer transition-colors hover:bg-[#d4705e]"; const btnOutlineCls = "px-4 py-[0.6rem] bg-transparent text-[#666] border-2 border-[#ddd] rounded-md text-base cursor-pointer transition-all hover:border-[#aaa] hover:text-[#333]"; +//Pagination button +//Highlighted when it represents the current page function PaginationBtn({ children, active, ...props }) { return ( + <> +
+ {apptSlice.map((a) => ( +
+
+ {a.serviceName} + + {a.appointmentStatus} + +
+
+ {a.storeName} + {a.appointmentDate} at {formatTime(a.appointmentTime)} +
+ {a.petName && ( +
Pet: {a.petName}
+ )} +
+ +
+ ))} +
+ {apptTotalPages > 1 && ( +
+ + Page {apptPage + 1} of {apptTotalPages} +
- ))} - + )} + )} {pastAppts.length > 0 && (
- {showPastAppts && ( -
- {pastAppts.map((a) => ( -
-
- {a.serviceName} - - {a.appointmentStatus} - + <> +
+ {pastApptSlice.map((a) => ( +
+
+ {a.serviceName} + + {a.appointmentStatus} + +
+
+ {a.storeName} + {a.appointmentDate} at {formatTime(a.appointmentTime)} +
+ {a.petName && ( +
Pet: {a.petName}
+ )}
-
- {a.storeName} - {a.appointmentDate} at {formatTime(a.appointmentTime)} -
- {a.petName && ( -
Pet: {a.petName}
- )} + ))} +
+ {pastApptTotalPages > 1 && ( +
+ + Page {pastApptPage + 1} of {pastApptTotalPages} +
- ))} -
+ )} + )}
)} @@ -918,6 +1015,12 @@ function AppointmentsPage() { const filteredActive = activeAdoptions.filter((a) => !q || [a.petName, a.sourceStoreName].some((v) => v?.toLowerCase().includes(q)) ); + //Paginate active adoptions + const adoptionTotalPages = Math.ceil(filteredActive.length / HISTORY_PAGE_SIZE); + const adoptionSlice = filteredActive.slice(adoptionPage * HISTORY_PAGE_SIZE, (adoptionPage + 1) * HISTORY_PAGE_SIZE); + //Paginate past adoptions + const pastAdoptionTotalPages = Math.ceil(pastAdoptions.length / HISTORY_PAGE_SIZE); + const pastAdoptionSlice = pastAdoptions.slice(pastAdoptionPage * HISTORY_PAGE_SIZE, (pastAdoptionPage + 1) * HISTORY_PAGE_SIZE); return ( <> {activeAdoptions.length === 0 ? "No active adoption requests." : "No results."}

) : ( -
- {filteredActive.map((a) => ( -
-
- {a.petName} - - {a.adoptionStatus} - -
-
- {a.sourceStoreName} - {a.adoptionDate} -
-
- + <> +
+ {adoptionSlice.map((a) => ( +
+
+ {a.petName} + + {a.adoptionStatus} + +
+
+ {a.sourceStoreName} + {a.adoptionDate} +
+
+ +
+ ))} +
+ {adoptionTotalPages > 1 && ( +
+ + Page {adoptionPage + 1} of {adoptionTotalPages} +
- ))} -
+ )} + )} {pastAdoptions.length > 0 && (
- {showPastAdoptions && ( -
- {pastAdoptions.map((a) => ( -
-
- {a.petName} - - {a.adoptionStatus} - -
-
- {a.sourceStoreName} - {a.adoptionDate} + <> +
+ {pastAdoptionSlice.map((a) => ( +
+
+ {a.petName} + + {a.adoptionStatus} + +
+
+ {a.sourceStoreName} + {a.adoptionDate} +
+ ))} +
+ {pastAdoptionTotalPages > 1 && ( +
+ + Page {pastAdoptionPage + 1} of {pastAdoptionTotalPages} +
- ))} -
+ )} + )}
)} diff --git a/web/app/cart/page.js b/web/app/cart/page.js index a7bd0d2a..b3e102b3 100644 --- a/web/app/cart/page.js +++ b/web/app/cart/page.js @@ -13,8 +13,10 @@ import { } from "@stripe/react-stripe-js"; import { apiCompleteCheckout } from "@/lib/cartApi"; +//Initializes Stripe with the publishable key const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || ""); +//Stripe payment form shown after the user clicks checkout function PaymentForm({ clientSecret, totalAmount, onSuccess, onCancel }) { const stripe = useStripe(); const elements = useElements(); @@ -22,6 +24,7 @@ function PaymentForm({ clientSecret, totalAmount, onSuccess, onCancel }) { const [paying, setPaying] = useState(false); const [payError, setPayError] = useState(null); + //Confirms the payment with Stripe, then tells the backend to complete the order async function handlePay(e) { e.preventDefault(); if (!stripe || !elements) return; @@ -57,9 +60,6 @@ function PaymentForm({ clientSecret, totalAmount, onSuccess, onCancel }) {

Total to pay: ${parseFloat(totalAmount).toFixed(2)}

-
-
Demo mode — no real charge. Use test card: 4242 4242 4242 4242 · any future date · any 3-digit CVC
-
{payError &&

{payError}

}
@@ -79,6 +79,7 @@ function PaymentForm({ clientSecret, totalAmount, onSuccess, onCancel }) { ); } +//Cart page - shows items, coupons, loyalty points, order summary, and checkout export default function CartPage() { const { user, loading: authLoading, refreshUser } = useAuth(); const { @@ -114,12 +115,14 @@ export default function CartPage() { const [localQuantities, setLocalQuantities] = useState({}); + //Redirect unauthenticated users to login useEffect(() => { if (!authLoading && !user) { router.push("/login"); } }, [authLoading, user, router]); + //Sync local quantity inputs whenever the cart updates from the server useEffect(() => { if (cart?.items) { const map = {}; @@ -129,12 +132,14 @@ export default function CartPage() { setOptimisticPointsApplied(null); }, [cart]); + //Cancel any leftover pending checkout if the page loads without a client secret useEffect(() => { if (cart?.checkoutPending && !clientSecret) { cancelCheckout().catch(() => {}); } }, [cart?.checkoutPending, clientSecret, cancelCheckout]); + //Updates item quantity and rolls back the change if the request fails async function handleQuantityChange(cartItemId, newQty) { if (newQty < 1) return; setLocalQuantities((prev) => ({ ...prev, [cartItemId]: newQty })); @@ -158,6 +163,7 @@ export default function CartPage() { catch {} } + //Applies the typed coupon code and shows the discount type and amount async function handleApplyCoupon() { if (!couponInput.trim()) return; setCouponLoading(true); @@ -212,6 +218,8 @@ export default function CartPage() { } } + //Starts checkout + //Either gets a Stripe client secret for payment or marks the order complete directly async function handleCheckout() { if (!cart?.items?.length) return; setCheckoutLoading(true); @@ -497,7 +505,7 @@ export default function CartPage() { )} {clientSecret && ( - + { if (!token || !convId) return; initialLoadDoneRef.current = false; @@ -145,6 +149,7 @@ function ChatPage() { } }, [token]); + //Fetches a single conversation's details and stores it in state const fetchConversation = useCallback(async (convId) => { if (!token || !convId) return null; try { @@ -183,6 +188,7 @@ function ChatPage() { } }, [token]); + //Connects the WebSocket and subscribes to new messages and conversation updates const connectStomp = useCallback((convId) => { if (stompRef.current) { stompRef.current.deactivate(); @@ -268,6 +274,7 @@ function ChatPage() { }; }, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations]); + //Decides whether to send a text message or an attachment async function handleSend(e) { e?.preventDefault(); const text = input.trim(); @@ -279,6 +286,7 @@ function ChatPage() { } } + //Sends a plain text message to the support agent async function handleSendText(text) { setInput(""); setSending(true); @@ -321,6 +329,7 @@ function ChatPage() { } } + //Uploads a file as an attachment, with an optional caption async function handleSendAttachment(optionalText) { setSending(true); setError(null); @@ -381,6 +390,7 @@ function ChatPage() { } } + //Creates a new live support conversation and requests a human agent straight away async function handleNewConversation() { setError(null); setLoadingConv(true); @@ -425,6 +435,7 @@ function ChatPage() { router.replace(`/chat?id=${convId}`, { scroll: false }); } + //Closes the current conversation so no more messages can be sent async function handleCloseConversation() { if (!conversation || conversation.status === "CLOSED") return; try { diff --git a/web/app/contact/page.js b/web/app/contact/page.js index 94fd00d1..da7e6bd2 100644 --- a/web/app/contact/page.js +++ b/web/app/contact/page.js @@ -7,6 +7,7 @@ const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[ const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; const submitBtnCls = "mt-2 py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; +//Returns the image path for a store, guessing from the store name if no image is set function getStoreImage(store) { if (store.imageUrl) return store.imageUrl; const name = store.storeName?.toLowerCase() ?? ""; @@ -16,6 +17,7 @@ function getStoreImage(store) { return "/images/pet-placeholder.png"; } +//Contact page with a message form on the left and store location cards on the right export default function ContactPage() { const { token } = useAuth(); const [locations, setLocations] = useState([]); @@ -28,6 +30,7 @@ export default function ContactPage() { const [sendError, setSendError] = useState(null); const [sendSuccess, setSendSuccess] = useState(false); + //Loads all store locations when the page first opens useEffect(() => { const params = new URLSearchParams({ page: "0", size: "100", sort: "storeName,asc" }); fetch(`/api/v1/stores?${params}`) @@ -40,6 +43,7 @@ export default function ContactPage() { .finally(() => setLoading(false)); }, []); + //Submits the contact form to the backend async function handleSend(e) { e.preventDefault(); if (!token) { diff --git a/web/app/forgot-password/page.js b/web/app/forgot-password/page.js index 753e8d07..4664a2cb 100644 --- a/web/app/forgot-password/page.js +++ b/web/app/forgot-password/page.js @@ -8,6 +8,7 @@ const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[ const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; const submitBtnCls = "mt-2 py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; +//Forgot password page, sends a reset link to the user's email function ForgotPasswordPage() { const [usernameOrEmail, setUsernameOrEmail] = useState(""); const [message, setMessage] = useState(""); @@ -15,6 +16,7 @@ function ForgotPasswordPage() { const [loading, setLoading] = useState(false); const [submitted, setSubmitted] = useState(false); + //Sends the forgot password request and hides the form on success async function handleSubmit(e) { e.preventDefault(); setError(""); diff --git a/web/app/layout.js b/web/app/layout.js index d1466ee6..4e867680 100644 --- a/web/app/layout.js +++ b/web/app/layout.js @@ -4,11 +4,14 @@ import DisplayNav from "@/components/Navigation"; import Footer from "@/components/Footer"; import ClientProviders from "@/components/ClientProviders"; +//Page title and description export const metadata = { title: "Leon's Pet Store", description: "Generated by create next app", }; +//Root layout +//Wraps every page with the nav, footer, and all context providers export default function RootLayout({children}) { return ( diff --git a/web/app/login/page.js b/web/app/login/page.js index 19344b19..4bdeba2a 100644 --- a/web/app/login/page.js +++ b/web/app/login/page.js @@ -10,12 +10,15 @@ const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[ const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; const submitBtnCls = "mt-2 py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; +//Returns the redirect path after login, blocking open redirects to external or auth pages function resolveNextPath(candidate) { if (!candidate || !candidate.startsWith("/")) return "/"; if (candidate.startsWith("//") || candidate.startsWith("/login") || candidate.startsWith("/register")) return "/"; return candidate; } +//Login page +//Username and password form, redirects to the page the user was trying to visit function LoginPage() { const {login} = useAuth(); const router = useRouter(); @@ -26,6 +29,7 @@ function LoginPage() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); + //Submits the login form and redirects on success async function handleSubmit(e) { e.preventDefault(); setError(""); diff --git a/web/app/page.js b/web/app/page.js index a9394ecf..00d80362 100644 --- a/web/app/page.js +++ b/web/app/page.js @@ -4,6 +4,7 @@ import Image from "next/image"; import Link from "next/link"; import { useState, useEffect } from "react"; +//Images used in the auto-scrolling slideshow at the top of the home page const slideshowImages = [ {id: "slide-1", src: "/images/home/slideshow/pet1.jpg", alt: "Happy pets"}, {id: "slide-2", src: "/images/home/slideshow/pet2.jpg", alt: "Pet supplies"}, @@ -11,15 +12,18 @@ const slideshowImages = [ {id: "slide-4", src: "/images/home/slideshow/pet4.jpg", alt: "Pet food"}, ]; +//Image cards linking to the three main sections of the site const navImages = [ {id: "nav-adopt", src: "/images/home/navimages/adopt.jpg", alt: "Adopt a Pet", link: "/adopt", title: "Adopt a Pet"}, {id: "nav-products", src: "/images/home/navimages/store.jpg", alt: "Online Store", link: "/products", title: "Online Store"}, {id: "nav-appointments", src: "/images/home/navimages/appointments.jpg", alt: "Appointments", link: "/appointments", title: "Appointments"}, ]; +//Home page - slideshow, nav image links, and about us section export default function Home() { const [currentSlide, setCurrentSlide] = useState(0); + //Advances the slideshow every 7.5 seconds useEffect(() => { const timer = setInterval(() => { setCurrentSlide((prev) => (prev + 1) % slideshowImages.length); }, 7500); return () => clearInterval(timer); diff --git a/web/app/products/[id]/page.js b/web/app/products/[id]/page.js index 6add2d5f..5808c51e 100644 --- a/web/app/products/[id]/page.js +++ b/web/app/products/[id]/page.js @@ -7,6 +7,7 @@ import ProductProfile from "@/components/ProductProfile"; const API_BASE = ""; +//Product detail page, fetches a single product by ID and passes it to ProductProfile export default function ProductDetailPage() { const { id } = useParams(); const [product, setProduct] = useState(null); diff --git a/web/app/products/page.js b/web/app/products/page.js index 155ba778..d1222a18 100644 --- a/web/app/products/page.js +++ b/web/app/products/page.js @@ -4,6 +4,8 @@ import { useState, useEffect } from "react"; import ProductCard from "@/components/ProductCard"; import { fetchAllPages } from "@/lib/fetchAllPages"; +//Products page +//Searchable grid of all store products with pagination const API_BASE = ""; export default function ProductsPage() { @@ -34,6 +36,7 @@ export default function ProductsPage() { const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE); const displayedProducts = products.slice(currentPage * ITEMS_PER_PAGE, (currentPage + 1) * ITEMS_PER_PAGE); + //Submits the search form function handleSearch(e) { e.preventDefault(); setLoading(true); diff --git a/web/app/profile/page.js b/web/app/profile/page.js index 39cc17dc..591d5c6e 100644 --- a/web/app/profile/page.js +++ b/web/app/profile/page.js @@ -6,6 +6,7 @@ import { useAuth } from "@/context/AuthContext"; const API_BASE = ""; +//Species and breed options for the add/edit pet form const SPECIES_BREEDS = { Dog: ["Beagle", "Boxer", "Bulldog", "Chihuahua", "Dachshund", "German Shepherd", "Golden Retriever", "Labrador Retriever", "Poodle", "Rottweiler", "Shih Tzu", "Siberian Husky", "Yorkshire Terrier", "Mixed / Other"], Cat: ["Abyssinian", "Bengal", "British Shorthair", "Maine Coon", "Persian", "Ragdoll", "Scottish Fold", "Siamese", "Sphynx", "Mixed / Other"], @@ -19,12 +20,13 @@ const SPECIES_BREEDS = { }; const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[#444]"; -const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; +const inputCls = "px-[0.85rem] py-[0.6rem] bg-white border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; const selectCls = `custom-select ${inputCls} bg-white cursor-pointer`; const errorCls = "bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b] rounded-lg px-4 py-3 text-[0.9rem]"; const successCls = "bg-[#f0fdf4] border border-[#bbf7d0] text-[#16a34a] rounded-lg px-4 py-3 text-[0.9rem]"; const submitBtnCls = "py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; +//Profile page - shows user info, edit form, avatar upload, owned pets, and order history export default function ProfilePage() { const {user, token, loading, logout, refreshUser} = useAuth(); const router = useRouter(); @@ -49,6 +51,7 @@ export default function ProfilePage() { const [profileSuccess, setProfileSuccess] = useState(null); const [avatarSubmitting, setAvatarSubmitting] = useState(false); + //Revokes all blob URLs created for pet images to free up memory const clearPetImageObjectUrls = useCallback(() => { for (const objectUrl of petImageObjectUrlsRef.current) { URL.revokeObjectURL(objectUrl); @@ -73,6 +76,7 @@ export default function ProfilePage() { }); }, [user]); + //Fetches the user's owned pets and resolves their images into blob URLs const loadPets = useCallback(async () => { if (!token) return; setLoadingPets(true); @@ -128,6 +132,7 @@ export default function ProfilePage() { }; }, [clearPetImageObjectUrls]); + //Fetches the user's recent order history const loadOrders = useCallback(async () => { if (!token) return; setLoadingOrders(true); @@ -176,11 +181,13 @@ export default function ProfilePage() { }; }, [user?.avatarUrl, token]); + //Logs out and sends the user to the home page function handleLogout() { logout(); router.push("/"); } + //Saves changes to the user's name, email, phone, or password async function handleProfileSubmit(e) { e.preventDefault(); setProfileError(null); @@ -232,6 +239,7 @@ export default function ProfilePage() { } } + //Uploads a new avatar image for the user async function handleAvatarUpload(file) { if (!file) return; @@ -266,6 +274,7 @@ export default function ProfilePage() { } } + //Removes the user's avatar async function handleAvatarDelete() { setAvatarSubmitting(true); setProfileError(null); @@ -295,6 +304,7 @@ export default function ProfilePage() { } } + //Opens the add pet form with blank fields function openAddForm() { setEditingPet(null); setPetName(""); @@ -305,6 +315,7 @@ export default function ProfilePage() { setShowForm(true); } + //Opens the edit pet form pre-filled with the selected pet's details function openEditForm(pet) { setEditingPet(pet); setPetName(pet.petName); @@ -321,6 +332,7 @@ export default function ProfilePage() { setPetError(null); } + //Creates a new pet or saves edits to an existing one async function handlePetSubmit(e) { e.preventDefault(); setPetError(null); @@ -358,6 +370,7 @@ export default function ProfilePage() { } } + //Confirms with the user then removes the pet profile async function handleDeletePet(id) { if (!confirm("Remove this pet profile?")) return; @@ -378,6 +391,7 @@ export default function ProfilePage() { } } + //Uploads a photo for a specific pet profile async function handleImageUpload(petId, file) { const formData = new FormData(); formData.append("image", file); diff --git a/web/app/register/page.js b/web/app/register/page.js index c3ea2ef0..1948ea57 100644 --- a/web/app/register/page.js +++ b/web/app/register/page.js @@ -20,6 +20,8 @@ function resolveNextPath(candidate) { return candidate; } +//Registration page +//Collects user details, checks passwords match, then creates an account function RegisterPage() { const {register} = useAuth(); const router = useRouter(); @@ -38,10 +40,12 @@ function RegisterPage() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); + //Updates a single field in the form state function handleChange(e) { setForm((prev) => ({ ...prev, [e.target.name]: e.target.value })); } + //Validates passwords match then submits the registration form async function handleSubmit(e) { e.preventDefault(); setError(""); diff --git a/web/app/reset-password/page.js b/web/app/reset-password/page.js index a9e3478c..6cfaeb7a 100644 --- a/web/app/reset-password/page.js +++ b/web/app/reset-password/page.js @@ -9,6 +9,7 @@ const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[ const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; const submitBtnCls = "mt-2 py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; +//Reset password page, reads the token from the URL and lets the user set a new password function ResetPasswordPage() { const router = useRouter(); const searchParams = useSearchParams(); @@ -36,6 +37,7 @@ function ResetPasswordPage() { ); } + //Validates passwords match, submits the reset, then redirects to login after 3 seconds async function handleSubmit(e) { e.preventDefault(); setError(""); diff --git a/web/components/ClientProviders.js b/web/components/ClientProviders.js index a5c6f9f3..73c7bb66 100644 --- a/web/components/ClientProviders.js +++ b/web/components/ClientProviders.js @@ -5,6 +5,7 @@ import { CartProvider } from "@/context/CartContext"; import { ChatWidgetProvider } from "@/context/ChatWidgetContext"; import FloatingChat from "@/components/FloatingChat"; +//Wraps the app in all client-side context providers and adds the floating chat button export default function ClientProviders({ children }) { return ( diff --git a/web/components/FloatingChat.js b/web/components/FloatingChat.js index 741db28a..505c46ed 100644 --- a/web/components/FloatingChat.js +++ b/web/components/FloatingChat.js @@ -6,6 +6,7 @@ import { usePathname } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; import { useChatWidget } from "@/context/ChatWidgetContext"; +//Floating chat button and popup window - handles AI chat, live support, and conversation history export default function FloatingChat() { const pathname = usePathname(); const { user, token } = useAuth(); @@ -25,6 +26,7 @@ export default function FloatingChat() { const prevAiLengthRef = useRef(0); const prevLiveLengthRef = useRef(0); + //Scrolls to the bottom when new messages arrive, but only if already near the bottom useEffect(() => { if (!isOpen) return; const aiGrew = aiMessages.length > prevAiLengthRef.current; @@ -41,12 +43,13 @@ const prevLiveLengthRef = useRef(0); if (view === "history" && token && isOpen) loadConversations(token); }, [view, token, isOpen, loadConversations]); - // Hide widget on dedicated chat pages + //Don't show the widget on the full chat pages since they have their own UI if (pathname === "/ai-chat" || pathname === "/chat") return null; const openConvCount = conversations.filter((c) => c.status === "OPEN").length; const isLiveClosed = activeConv?.status === "CLOSED"; + //Sends the typed message to whichever chat is currently active async function handleSend(e) { e?.preventDefault(); const text = input.trim(); @@ -358,7 +361,7 @@ const prevLiveLengthRef = useRef(0); ); } -// Styles +//Inline style objects for the floating chat widget const s = { fab: { position: "fixed", bottom: 24, right: 24, diff --git a/web/components/Footer.js b/web/components/Footer.js index c208793d..20b1f640 100644 --- a/web/components/Footer.js +++ b/web/components/Footer.js @@ -1,8 +1,10 @@ import Link from "next/link"; import Image from "next/image"; +//Shared CSS class for footer links const linkCls = "text-[#2f2f2f] no-underline text-[0.95rem] opacity-85 transition-opacity hover:opacity-100 hover:underline"; +//Site footer with quick links, company links, and contact info export default function Footer() { return (