From 3e74cdd25e6877acbc18fe513457f4b127e4357f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 19:34:00 -0600 Subject: [PATCH 01/15] Add target DB setup --- backend/docker-compose.target-db.yml | 25 + backend/pom.xml | 51 + .../db/migration/V10__adoption_service.sql | 2 - .../V11__appointment_customer_pet.sql | 7 - .../migration/V12__backfill_user_accounts.sql | 91 - .../V13__remove_debit_payment_method.sql | 3 - .../migration/V14__consolidated_updates.sql | 33 - ...appointment_adoption_employee_required.sql | 61 - .../migration/V16__activate_all_employees.sql | 4 - .../V17__normalize_appointment_pets.sql | 22 - .../V18__past_appointments_missed.sql | 40 - .../db/migration/V19__pet_owner_and_store.sql | 23 - .../db/migration/V1__baseline_schema.sql | 250 --- .../db/migration/V1__target_baseline.sql | 341 +++ .../db/migration/V20__seed_owned_pets.sql | 6 - .../V21__bulk_seed_pets_and_appointments.sql | 161 -- .../resources/db/migration/V2__seed_data.sql | 205 -- ...t_store_and_employee_store_constraints.sql | 19 - .../V4__conversation_mode_and_takeover.sql | 9 - .../db/migration/V5__user_token_version.sql | 2 - .../resources/db/migration/V6__user_phone.sql | 8 - .../V7__employee_customer_phone_cutover.sql | 11 - .../migration/V8__pet_product_image_urls.sql | 5 - .../db/migration/V9__customer_pet.sql | 11 - .../dev/final-target/final_target_schema.sql | 343 +++ .../dev/final-target/final_target_seed.sql | 1861 +++++++++++++++++ 26 files changed, 2621 insertions(+), 973 deletions(-) create mode 100644 backend/docker-compose.target-db.yml delete mode 100644 backend/src/main/resources/db/migration/V10__adoption_service.sql delete mode 100644 backend/src/main/resources/db/migration/V11__appointment_customer_pet.sql delete mode 100644 backend/src/main/resources/db/migration/V12__backfill_user_accounts.sql delete mode 100644 backend/src/main/resources/db/migration/V13__remove_debit_payment_method.sql delete mode 100644 backend/src/main/resources/db/migration/V14__consolidated_updates.sql delete mode 100644 backend/src/main/resources/db/migration/V15__appointment_adoption_employee_required.sql delete mode 100644 backend/src/main/resources/db/migration/V16__activate_all_employees.sql delete mode 100644 backend/src/main/resources/db/migration/V17__normalize_appointment_pets.sql delete mode 100644 backend/src/main/resources/db/migration/V18__past_appointments_missed.sql delete mode 100644 backend/src/main/resources/db/migration/V19__pet_owner_and_store.sql delete mode 100644 backend/src/main/resources/db/migration/V1__baseline_schema.sql create mode 100644 backend/src/main/resources/db/migration/V1__target_baseline.sql delete mode 100644 backend/src/main/resources/db/migration/V20__seed_owned_pets.sql delete mode 100644 backend/src/main/resources/db/migration/V21__bulk_seed_pets_and_appointments.sql delete mode 100644 backend/src/main/resources/db/migration/V2__seed_data.sql delete mode 100644 backend/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql delete mode 100644 backend/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql delete mode 100644 backend/src/main/resources/db/migration/V5__user_token_version.sql delete mode 100644 backend/src/main/resources/db/migration/V6__user_phone.sql delete mode 100644 backend/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql delete mode 100644 backend/src/main/resources/db/migration/V8__pet_product_image_urls.sql delete mode 100644 backend/src/main/resources/db/migration/V9__customer_pet.sql create mode 100644 backend/src/main/resources/dev/final-target/final_target_schema.sql create mode 100644 backend/src/main/resources/dev/final-target/final_target_seed.sql diff --git a/backend/docker-compose.target-db.yml b/backend/docker-compose.target-db.yml new file mode 100644 index 00000000..03c05785 --- /dev/null +++ b/backend/docker-compose.target-db.yml @@ -0,0 +1,25 @@ +services: + db-target: + image: mysql:8.0 + container_name: petshop-db-target + restart: always + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: Petstoredb_target + MYSQL_USER: petshop + MYSQL_PASSWORD: petshop + ports: + - "3307:3306" + volumes: + - db_target_data:/var/lib/mysql + - ./src/main/resources/dev/final-target/final_target_schema.sql:/docker-entrypoint-initdb.d/01_final_target_schema.sql:ro + - ./src/main/resources/dev/final-target/final_target_seed.sql:/docker-entrypoint-initdb.d/02_final_target_seed.sql:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 40s + +volumes: + db_target_data: diff --git a/backend/pom.xml b/backend/pom.xml index b511e715..e1c93bd0 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -197,6 +197,57 @@ + + docker-up-target-db + + exec + + + docker + + compose + -f + docker-compose.target-db.yml + up + -d + --wait + db-target + + + + + docker-down-target-db + + exec + + + docker + + compose + -f + docker-compose.target-db.yml + down + -v + --remove-orphans + + + + + docker-logs-target-db + + exec + + + docker + + compose + -f + docker-compose.target-db.yml + logs + db-target + + + diff --git a/backend/src/main/resources/db/migration/V10__adoption_service.sql b/backend/src/main/resources/db/migration/V10__adoption_service.sql deleted file mode 100644 index dd5a57ca..00000000 --- a/backend/src/main/resources/db/migration/V10__adoption_service.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) -VALUES ('Pet Adoption', 'Schedule a visit to meet and adopt an available pet', 30, 0.00); diff --git a/backend/src/main/resources/db/migration/V11__appointment_customer_pet.sql b/backend/src/main/resources/db/migration/V11__appointment_customer_pet.sql deleted file mode 100644 index d112fda0..00000000 --- a/backend/src/main/resources/db/migration/V11__appointment_customer_pet.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS appointment_customer_pet ( - appointment_id BIGINT NOT NULL, - customer_pet_id BIGINT NOT NULL, - PRIMARY KEY (appointment_id, customer_pet_id), - FOREIGN KEY (appointment_id) REFERENCES appointment(appointmentId), - FOREIGN KEY (customer_pet_id) REFERENCES customer_pet(customer_pet_id) -); diff --git a/backend/src/main/resources/db/migration/V12__backfill_user_accounts.sql b/backend/src/main/resources/db/migration/V12__backfill_user_accounts.sql deleted file mode 100644 index 4af69669..00000000 --- a/backend/src/main/resources/db/migration/V12__backfill_user_accounts.sql +++ /dev/null @@ -1,91 +0,0 @@ -INSERT INTO users (username, password, email, fullName, phone, role, active, tokenVersion) -SELECT - CONCAT('customer_', c.customerId) AS username, - '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq' AS password, - CASE - WHEN c.email IS NOT NULL - AND c.email <> '' - AND (SELECT COUNT(*) FROM customer c2 WHERE c2.email = c.email) = 1 - AND NOT EXISTS (SELECT 1 FROM employee e2 WHERE e2.email = c.email) - AND NOT EXISTS (SELECT 1 FROM users u WHERE u.email = c.email) - THEN c.email - ELSE CONCAT('customer_', c.customerId, '@petshop.local') - END AS email, - CONCAT(c.firstName, ' ', c.lastName) AS fullName, - CONCAT('200-000-', LPAD(c.customerId, 4, '0')) AS phone, - 'CUSTOMER' AS role, - FALSE AS active, - 0 AS tokenVersion -FROM customer c -WHERE c.user_id IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM users u - WHERE u.username = CONCAT('customer_', c.customerId) - ); - -INSERT INTO users (username, password, email, fullName, phone, role, active, tokenVersion) -SELECT - CONCAT('employee_', e.employeeId) AS username, - '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq' AS password, - CASE - WHEN e.email IS NOT NULL - AND e.email <> '' - AND (SELECT COUNT(*) FROM employee e2 WHERE e2.email = e.email) = 1 - AND NOT EXISTS (SELECT 1 FROM customer c2 WHERE c2.email = e.email) - AND NOT EXISTS (SELECT 1 FROM users u WHERE u.email = e.email) - THEN e.email - ELSE CONCAT('employee_', e.employeeId, '@petshop.local') - END AS email, - CONCAT(e.firstName, ' ', e.lastName) AS fullName, - CONCAT('300-000-', LPAD(e.employeeId, 4, '0')) AS phone, - CASE - WHEN UPPER(e.role) = 'MANAGER' THEN 'ADMIN' - ELSE 'STAFF' - END AS role, - FALSE AS active, - 0 AS tokenVersion -FROM employee e -WHERE e.user_id IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM users u - WHERE u.username = CONCAT('employee_', e.employeeId) - ); - -UPDATE customer c -JOIN users u ON u.username = CONCAT('customer_', c.customerId) - AND u.role = 'CUSTOMER' -SET c.user_id = u.id -WHERE c.user_id IS NULL; - -UPDATE employee e -JOIN users u ON u.username = CONCAT('employee_', e.employeeId) - AND u.role IN ('STAFF', 'ADMIN') -SET e.user_id = u.id -WHERE e.user_id IS NULL; - -UPDATE users -SET - fullName = CASE - WHEN fullName IS NULL OR fullName = '' THEN username - ELSE fullName - END, - email = CASE - WHEN email IS NULL OR email = '' THEN CONCAT(username, '@petshop.local') - ELSE email - END, - phone = CASE - WHEN phone IS NULL OR phone = '' THEN CONCAT('000-000-', LPAD(id, 4, '0')) - ELSE phone - END, - active = COALESCE(active, TRUE), - tokenVersion = COALESCE(tokenVersion, 0) -WHERE fullName IS NULL - OR fullName = '' - OR email IS NULL - OR email = '' - OR phone IS NULL - OR phone = '' - OR active IS NULL - OR tokenVersion IS NULL; diff --git a/backend/src/main/resources/db/migration/V13__remove_debit_payment_method.sql b/backend/src/main/resources/db/migration/V13__remove_debit_payment_method.sql deleted file mode 100644 index 874b0205..00000000 --- a/backend/src/main/resources/db/migration/V13__remove_debit_payment_method.sql +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE sale -SET paymentMethod = 'Card' -WHERE LOWER(paymentMethod) = 'debit'; diff --git a/backend/src/main/resources/db/migration/V14__consolidated_updates.sql b/backend/src/main/resources/db/migration/V14__consolidated_updates.sql deleted file mode 100644 index eb0486a8..00000000 --- a/backend/src/main/resources/db/migration/V14__consolidated_updates.sql +++ /dev/null @@ -1,33 +0,0 @@ --- Consolidated Updates: Phone Normalization and Refund Items - --- 1. Create refund_item table -CREATE TABLE IF NOT EXISTS refund_item ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - refund_id BIGINT NOT NULL, - prod_id BIGINT NOT NULL, - quantity INT NOT NULL, - unit_price DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (refund_id) REFERENCES refund(id) ON DELETE CASCADE, - FOREIGN KEY (prod_id) REFERENCES product(prodId) -); - --- 2. Normalize existing phone numbers (MySQL Set-based) -UPDATE users -SET phone = CONCAT('(', SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -10, 3), ') ', - SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -7, 3), '-', - SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -4)) -WHERE phone REGEXP '[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9]'; - -UPDATE supplier -SET supPhone = CONCAT('(', SUBSTRING(REGEXP_REPLACE(supPhone, '[^0-9]', ''), -10, 3), ') ', - SUBSTRING(REGEXP_REPLACE(supPhone, '[^0-9]', ''), -7, 3), '-', - SUBSTRING(REGEXP_REPLACE(supPhone, '[^0-9]', ''), -4)) -WHERE supPhone REGEXP '[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9]'; - -UPDATE storeLocation -SET phone = CONCAT('(', SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -10, 3), ') ', - SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -7, 3), '-', - SUBSTRING(REGEXP_REPLACE(phone, '[^0-9]', ''), -4)) -WHERE phone REGEXP '[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9].*[0-9]'; diff --git a/backend/src/main/resources/db/migration/V15__appointment_adoption_employee_required.sql b/backend/src/main/resources/db/migration/V15__appointment_adoption_employee_required.sql deleted file mode 100644 index e931e32a..00000000 --- a/backend/src/main/resources/db/migration/V15__appointment_adoption_employee_required.sql +++ /dev/null @@ -1,61 +0,0 @@ -ALTER TABLE appointment - ADD COLUMN employeeId BIGINT NULL; - -UPDATE appointment a -SET a.employeeId = ( - SELECT es.employeeId - FROM employeeStore es - JOIN employee e ON e.employeeId = es.employeeId - JOIN users u ON u.id = e.user_id - WHERE es.storeId = a.storeId - AND e.isActive = TRUE - AND u.role = 'STAFF' - ORDER BY es.employeeId ASC - LIMIT 1 -) -WHERE a.employeeId IS NULL; - -UPDATE appointment a -SET a.employeeId = ( - SELECT e.employeeId - FROM employee e - JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE - AND u.role = 'STAFF' - ORDER BY e.employeeId ASC - LIMIT 1 -) -WHERE a.employeeId IS NULL; - -ALTER TABLE appointment - ADD CONSTRAINT fk_appointment_employee - FOREIGN KEY (employeeId) REFERENCES employee(employeeId); - -CREATE INDEX idx_appointment_employeeId ON appointment(employeeId); - -ALTER TABLE appointment - MODIFY employeeId BIGINT NOT NULL; - -ALTER TABLE adoption - ADD COLUMN employeeId BIGINT NULL; - -UPDATE adoption a -SET a.employeeId = ( - SELECT e.employeeId - FROM employee e - JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE - AND u.role = 'STAFF' - ORDER BY e.employeeId ASC - LIMIT 1 -) -WHERE a.employeeId IS NULL; - -ALTER TABLE adoption - ADD CONSTRAINT fk_adoption_employee - FOREIGN KEY (employeeId) REFERENCES employee(employeeId); - -CREATE INDEX idx_adoption_employeeId ON adoption(employeeId); - -ALTER TABLE adoption - MODIFY employeeId BIGINT NOT NULL; diff --git a/backend/src/main/resources/db/migration/V16__activate_all_employees.sql b/backend/src/main/resources/db/migration/V16__activate_all_employees.sql deleted file mode 100644 index 314c86c8..00000000 --- a/backend/src/main/resources/db/migration/V16__activate_all_employees.sql +++ /dev/null @@ -1,4 +0,0 @@ -UPDATE users u -SET u.active = TRUE -WHERE u.role IN ('STAFF', 'ADMIN') - AND EXISTS (SELECT 1 FROM employee e WHERE e.user_id = u.id); diff --git a/backend/src/main/resources/db/migration/V17__normalize_appointment_pets.sql b/backend/src/main/resources/db/migration/V17__normalize_appointment_pets.sql deleted file mode 100644 index 90c2a407..00000000 --- a/backend/src/main/resources/db/migration/V17__normalize_appointment_pets.sql +++ /dev/null @@ -1,22 +0,0 @@ -INSERT INTO customer_pet (customer_id, pet_name, species, breed) -SELECT DISTINCT a.customerId, p.petName, p.petSpecies, p.petBreed -FROM appointmentPet ap -JOIN appointment a ON a.appointmentId = ap.appointmentId -JOIN pet p ON p.petId = ap.petId -WHERE NOT EXISTS ( - SELECT 1 FROM customer_pet cp - WHERE cp.customer_id = a.customerId AND cp.pet_name = p.petName -); - -INSERT INTO appointment_customer_pet (appointment_id, customer_pet_id) -SELECT ap.appointmentId, cp.customer_pet_id -FROM appointmentPet ap -JOIN appointment a ON a.appointmentId = ap.appointmentId -JOIN pet p ON p.petId = ap.petId -JOIN customer_pet cp ON cp.customer_id = a.customerId AND cp.pet_name = p.petName -WHERE NOT EXISTS ( - SELECT 1 FROM appointment_customer_pet acp - WHERE acp.appointment_id = ap.appointmentId AND acp.customer_pet_id = cp.customer_pet_id -); - -DELETE FROM appointmentPet; diff --git a/backend/src/main/resources/db/migration/V18__past_appointments_missed.sql b/backend/src/main/resources/db/migration/V18__past_appointments_missed.sql deleted file mode 100644 index 093c6ce3..00000000 --- a/backend/src/main/resources/db/migration/V18__past_appointments_missed.sql +++ /dev/null @@ -1,40 +0,0 @@ -UPDATE appointment -SET appointmentStatus = 'Missed' -WHERE LOWER(appointmentStatus) = 'booked' - AND ( - appointmentDate < CURRENT_DATE - OR (appointmentDate = CURRENT_DATE AND appointmentTime < CURRENT_TIME) - ); - -UPDATE appointment a1 -JOIN ( - SELECT a3.appointmentId - FROM appointment a3 - INNER JOIN appointment a4 - ON a4.employeeId = a3.employeeId - AND a4.appointmentDate = a3.appointmentDate - AND a4.appointmentTime = a3.appointmentTime - AND a4.appointmentId < a3.appointmentId - WHERE LOWER(a3.appointmentStatus) NOT IN ('cancelled', 'missed') -) conflicting ON conflicting.appointmentId = a1.appointmentId -SET a1.employeeId = ( - SELECT es.employeeId - FROM employeeStore es - JOIN employee e ON e.employeeId = es.employeeId - JOIN users u ON u.id = e.user_id - WHERE es.storeId = a1.storeId - AND e.isActive = TRUE - AND u.role = 'STAFF' - AND NOT EXISTS ( - SELECT 1 FROM ( - SELECT employeeId, appointmentDate, appointmentTime, appointmentId - FROM appointment - ) snap - WHERE snap.employeeId = es.employeeId - AND snap.appointmentDate = a1.appointmentDate - AND snap.appointmentTime = a1.appointmentTime - AND snap.appointmentId <> a1.appointmentId - ) - ORDER BY es.employeeId ASC - LIMIT 1 -); diff --git a/backend/src/main/resources/db/migration/V19__pet_owner_and_store.sql b/backend/src/main/resources/db/migration/V19__pet_owner_and_store.sql deleted file mode 100644 index 918ed375..00000000 --- a/backend/src/main/resources/db/migration/V19__pet_owner_and_store.sql +++ /dev/null @@ -1,23 +0,0 @@ -ALTER TABLE pet ADD COLUMN customerId BIGINT NULL; -ALTER TABLE pet ADD COLUMN storeId BIGINT NULL; - -ALTER TABLE pet ADD CONSTRAINT fk_pet_customer - FOREIGN KEY (customerId) REFERENCES customer(customerId); -ALTER TABLE pet ADD CONSTRAINT fk_pet_store - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId); - -CREATE INDEX idx_pet_customerId ON pet(customerId); -CREATE INDEX idx_pet_storeId ON pet(storeId); - -UPDATE pet -SET storeId = (SELECT storeId FROM storeLocation ORDER BY storeId ASC LIMIT 1) -WHERE LOWER(petStatus) IN ('available', 'unadopted'); - -UPDATE pet p -JOIN ( - SELECT a.petId, a.customerId - FROM adoption a - WHERE LOWER(a.adoptionStatus) = 'completed' -) latest ON latest.petId = p.petId -SET p.customerId = latest.customerId -WHERE LOWER(p.petStatus) = 'adopted'; diff --git a/backend/src/main/resources/db/migration/V1__baseline_schema.sql b/backend/src/main/resources/db/migration/V1__baseline_schema.sql deleted file mode 100644 index ae1ca009..00000000 --- a/backend/src/main/resources/db/migration/V1__baseline_schema.sql +++ /dev/null @@ -1,250 +0,0 @@ --- Create Tables - -CREATE TABLE IF NOT EXISTS storeLocation ( - storeId BIGINT AUTO_INCREMENT PRIMARY KEY, - storeName VARCHAR(100) NOT NULL, - address VARCHAR(255) NOT NULL, - phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS employee ( - employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NULL, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - role VARCHAR(50) NOT NULL, - isActive BOOLEAN DEFAULT TRUE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT uk_employee_user_id UNIQUE (user_id) -); - -CREATE TABLE IF NOT EXISTS employeeStore ( - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - PRIMARY KEY (employeeId, storeId), - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) -); - -CREATE TABLE IF NOT EXISTS customer ( - customerId BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NULL, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT uk_customer_user_id UNIQUE (user_id) -); - -CREATE TABLE IF NOT EXISTS pet ( - petId BIGINT AUTO_INCREMENT PRIMARY KEY, - petName VARCHAR(50) NOT NULL, - petSpecies VARCHAR(50) NOT NULL, - petBreed VARCHAR(50) NOT NULL, - petAge INT NOT NULL, - petStatus VARCHAR(20) NOT NULL, - petPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS adoption ( - adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, - petId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - adoptionDate DATE NOT NULL, - adoptionStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (petId) REFERENCES pet(petId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS supplier ( - supId BIGINT AUTO_INCREMENT PRIMARY KEY, - supCompany VARCHAR(100) NOT NULL, - supContactFirstName VARCHAR(50) NOT NULL, - supContactLastName VARCHAR(50) NOT NULL, - supEmail VARCHAR(100) NOT NULL, - supPhone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS category ( - categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS product ( - prodId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodName VARCHAR(100) NOT NULL, - prodPrice DECIMAL(10, 2) NOT NULL, - categoryId BIGINT NOT NULL, - prodDesc TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (categoryId) REFERENCES category(categoryId) -); - -CREATE TABLE IF NOT EXISTS productSupplier ( - supId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - cost DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (supId, prodId), - FOREIGN KEY (supId) REFERENCES supplier(supId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS inventory ( - inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodId BIGINT NOT NULL, - quantity INT DEFAULT 0 NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS service ( - serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceName VARCHAR(100) NOT NULL, - serviceDesc TEXT, - serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS appointment ( - appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - appointmentDate DATE NOT NULL, - appointmentTime TIME NOT NULL, - appointmentStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (serviceId) REFERENCES service(serviceId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS appointmentPet ( - appointmentId BIGINT NOT NULL, - petId BIGINT NOT NULL, - PRIMARY KEY (appointmentId, petId), - FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), - FOREIGN KEY (petId) REFERENCES pet(petId) -); - -CREATE TABLE IF NOT EXISTS sale ( - saleId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleDate DATETIME NOT NULL, - totalAmount DECIMAL(10, 2) NOT NULL, - paymentMethod VARCHAR(50) NOT NULL, - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - customerId BIGINT NULL, - isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId BIGINT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - FOREIGN KEY (customerId) REFERENCES customer(customerId), - FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE IF NOT EXISTS saleItem ( - saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - quantity INT NOT NULL, - unitPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS purchaseOrder ( - purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, - supId 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, - FOREIGN KEY (supId) REFERENCES supplier(supId) -); - -CREATE TABLE IF NOT EXISTS activityLog ( - logId BIGINT AUTO_INCREMENT PRIMARY KEY, - employeeId BIGINT NOT NULL, - activity TEXT NOT NULL, - logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId) -); - -CREATE TABLE IF NOT EXISTS users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - email VARCHAR(100) UNIQUE, - fullName VARCHAR(100), - avatarUrl VARCHAR(255), - role VARCHAR(20) NOT NULL, - active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS refund ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - saleId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - amount DECIMAL(10, 2) NOT NULL, - reason VARCHAR(500) NOT NULL, - status VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS conversation ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - customerId BIGINT NOT NULL, - staffId BIGINT, - status VARCHAR(20) NOT NULL DEFAULT 'OPEN', - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (customerId) REFERENCES customer(customerId), - FOREIGN KEY (staffId) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS message ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - conversationId BIGINT NOT NULL, - senderId BIGINT NOT NULL, - content TEXT NOT NULL, - timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - isRead BOOLEAN NOT NULL DEFAULT FALSE, - FOREIGN KEY (conversationId) REFERENCES conversation(id), - FOREIGN KEY (senderId) REFERENCES users(id) -); - --- Add foreign keys for user_id linkage -ALTER TABLE employee ADD CONSTRAINT fk_employee_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; -ALTER TABLE customer ADD CONSTRAINT fk_customer_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql new file mode 100644 index 00000000..d287c8e1 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -0,0 +1,341 @@ + +CREATE TABLE IF NOT EXISTS storeLocation ( + storeId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL, + imageUrl VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NULL UNIQUE, + password VARCHAR(255) NULL, + email VARCHAR(100) NULL UNIQUE, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + fullName VARCHAR(100) NULL, + phone VARCHAR(20) NULL, + avatarUrl VARCHAR(255) NULL, + role VARCHAR(20) NOT NULL, + staffRole VARCHAR(50) NULL, + primaryStoreId BIGINT NULL, + loyaltyPoints INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT TRUE, + 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 +); + +CREATE TABLE IF NOT EXISTS supplier ( + supId BIGINT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS category ( + categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uq_category_name_type UNIQUE (categoryName, categoryType) +); + +CREATE TABLE IF NOT EXISTS service ( + serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT NULL, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS service_species ( + serviceId BIGINT NOT NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS product ( + prodId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId BIGINT NOT NULL, + prodDesc TEXT NULL, + imageUrl VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_product_category FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE IF NOT EXISTS inventory ( + inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + 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) +); + +CREATE TABLE IF NOT EXISTS productSupplier ( + supId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (supId, prodId), + CONSTRAINT fk_product_supplier_supplier FOREIGN KEY (supId) REFERENCES supplier(supId), + CONSTRAINT fk_product_supplier_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS purchaseOrder ( + purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, + 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), + CONSTRAINT fk_purchase_order_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE IF NOT EXISTS coupon ( + couponId BIGINT AUTO_INCREMENT PRIMARY KEY, + couponCode VARCHAR(50) NOT NULL, + discountType VARCHAR(20) NOT NULL, + discountValue DECIMAL(10, 2) NOT NULL, + minOrderAmount DECIMAL(10, 2) NULL, + active BOOLEAN NOT NULL DEFAULT TRUE, + startsAt DATETIME NULL, + endsAt DATETIME NULL, + usageLimit INT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uq_coupon_code UNIQUE (couponCode) +); + +CREATE TABLE IF NOT EXISTS pet ( + petId BIGINT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NULL, + petAge INT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NULL, + imageUrl VARCHAR(255) NULL, + ownerUserId BIGINT NULL, + storeId BIGINT NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS appointment ( + appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT NOT NULL, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + employeeId BIGINT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + 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_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) +); + +CREATE TABLE IF NOT EXISTS adoption ( + adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + employeeId BIGINT NOT NULL, + sourceStoreId BIGINT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + 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) +); + +CREATE TABLE IF NOT EXISTS cart ( + cartId BIGINT AUTO_INCREMENT PRIMARY KEY, + userId BIGINT NOT NULL, + storeId BIGINT NULL, + couponId BIGINT NULL, + cartStatus VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', + subtotalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + discountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + totalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + 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 +); + +CREATE TABLE IF NOT EXISTS cart_item ( + cartItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + cartId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_cart_item_cart FOREIGN KEY (cartId) REFERENCES cart(cartId) ON DELETE CASCADE, + CONSTRAINT fk_cart_item_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS sale ( + saleId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + customerId BIGINT NULL, + isRefund BOOLEAN NOT NULL DEFAULT FALSE, + originalSaleId BIGINT NULL, + channel VARCHAR(20) NOT NULL DEFAULT 'IN_STORE', + cartId BIGINT NULL, + couponId BIGINT NULL, + subtotalAmount DECIMAL(10, 2) NULL, + couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + pointsEarned INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id), + CONSTRAINT fk_sale_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + 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 +); + +CREATE TABLE IF NOT EXISTS saleItem ( + saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_sale_item_sale FOREIGN KEY (saleId) REFERENCES sale(saleId) ON DELETE CASCADE, + CONSTRAINT fk_sale_item_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS refund ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + reason VARCHAR(500) NOT NULL, + status VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_refund_sale FOREIGN KEY (saleId) REFERENCES sale(saleId), + CONSTRAINT fk_refund_customer FOREIGN KEY (customerId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS refund_item ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + refund_id BIGINT NOT NULL, + prod_id BIGINT NOT NULL, + quantity INT NOT NULL, + unit_price DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_refund_item_refund FOREIGN KEY (refund_id) REFERENCES refund(id) ON DELETE CASCADE, + CONSTRAINT fk_refund_item_product FOREIGN KEY (prod_id) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS conversation ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customerId BIGINT NOT NULL, + staffId BIGINT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'OPEN', + mode VARCHAR(20) NOT NULL DEFAULT 'AUTOMATED', + humanRequestedAt TIMESTAMP NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS message ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + conversationId BIGINT NOT NULL, + senderId BIGINT NOT NULL, + content TEXT NULL, + attachmentUrl VARCHAR(255) NULL, + attachmentName VARCHAR(255) NULL, + attachmentMimeType VARCHAR(100) NULL, + attachmentSizeBytes BIGINT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + isRead BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT fk_message_conversation FOREIGN KEY (conversationId) REFERENCES conversation(id) ON DELETE CASCADE, + CONSTRAINT fk_message_sender FOREIGN KEY (senderId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS activityLog ( + logId BIGINT AUTO_INCREMENT PRIMARY KEY, + userId BIGINT NOT NULL, + storeId BIGINT NULL, + 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 +); + +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); diff --git a/backend/src/main/resources/db/migration/V20__seed_owned_pets.sql b/backend/src/main/resources/db/migration/V20__seed_owned_pets.sql deleted file mode 100644 index 5b4f7928..00000000 --- a/backend/src/main/resources/db/migration/V20__seed_owned_pets.sql +++ /dev/null @@ -1,6 +0,0 @@ -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId) -SELECT 'Pepper', 'Cat', 'Tabby', 3, 'Owned', 0.00, customerId FROM customer WHERE email = 'alex@gmail.com' -UNION ALL -SELECT 'Coco', 'Dog', 'Pomeranian', 2, 'Owned', 0.00, customerId FROM customer WHERE email = 'olivia@gmail.com' -UNION ALL -SELECT 'Finn', 'Dog', 'Border Collie', 5, 'Owned', 0.00, customerId FROM customer WHERE email = 'sophia@gmail.com'; diff --git a/backend/src/main/resources/db/migration/V21__bulk_seed_pets_and_appointments.sql b/backend/src/main/resources/db/migration/V21__bulk_seed_pets_and_appointments.sql deleted file mode 100644 index 8f65a53d..00000000 --- a/backend/src/main/resources/db/migration/V21__bulk_seed_pets_and_appointments.sql +++ /dev/null @@ -1,161 +0,0 @@ --- Insert 10 new customers -INSERT INTO customer (firstName, lastName, email) VALUES -('Noah', 'Parker', 'noah@gmail.com'), -('Mia', 'Evans', 'mia@gmail.com'), -('Ethan', 'Scott', 'ethan@gmail.com'), -('Chloe', 'Adams', 'chloe@gmail.com'), -('Lucas', 'Baker', 'lucas@gmail.com'), -('Lily', 'Hall', 'lily@gmail.com'), -('Mason', 'Rivera', 'mason@gmail.com'), -('Ella', 'Mitchell', 'ella@gmail.com'), -('James', 'Carter', 'jcarter@gmail.com'), -('Harper', 'Collins', 'harper@gmail.com'); - --- Insert available pets linked to stores -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, storeId) -SELECT 'Rocky', 'Dog', 'German Shepherd', 1, 'Available', 475.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Daisy', 'Dog', 'Poodle', 2, 'Available', 512.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Cooper', 'Dog', 'Bulldog', 3, 'Available', 560.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Ruby', 'Dog', 'Boxer', 4, 'Available', 575.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Tucker', 'Dog', 'Dachshund', 5, 'Available', 634.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Rosie', 'Dog', 'Shih Tzu', 1, 'Available', 660.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Bear', 'Dog', 'Rottweiler', 2, 'Available', 686.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Maggie', 'Dog', 'Corgi', 3, 'Available', 745.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Leo', 'Dog', 'Husky', 4, 'Available', 749.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Zoey', 'Cat', 'Ragdoll', 1, 'Available', 420.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Oliver', 'Cat', 'British Shorthair', 2, 'Available', 395.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Lola', 'Cat', 'Bengal', 3, 'Available', 465.00, storeId FROM storeLocation WHERE storeName = 'West Side Store' UNION ALL -SELECT 'Buster', 'Dog', 'Beagle', 2, 'Available', 440.00, storeId FROM storeLocation WHERE storeName = 'West Side Store' UNION ALL -SELECT 'Sadie', 'Dog', 'Golden Retriever', 1, 'Available', 535.00, storeId FROM storeLocation WHERE storeName = 'West Side Store' UNION ALL -SELECT 'Toby', 'Dog', 'Labrador', 5, 'Available', 490.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Cleo', 'Cat', 'Abyssinian', 2, 'Available', 375.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Harley', 'Dog', 'Dalmatian', 3, 'Available', 520.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Mocha', 'Cat', 'Burmese', 1, 'Available', 345.00, storeId FROM storeLocation WHERE storeName = 'West Side Store' UNION ALL -SELECT 'Rex', 'Dog', 'Doberman', 4, 'Available', 610.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Willow', 'Cat', 'Scottish Fold', 2, 'Available', 480.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Gizmo', 'Dog', 'Pomeranian', 1, 'Available', 530.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Nala', 'Cat', 'Siamese', 3, 'Available', 360.00, storeId FROM storeLocation WHERE storeName = 'North Branch' UNION ALL -SELECT 'Duke', 'Dog', 'Great Dane', 2, 'Available', 720.00, storeId FROM storeLocation WHERE storeName = 'West Side Store' UNION ALL -SELECT 'Misty', 'Cat', 'Russian Blue', 4, 'Available', 410.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch' UNION ALL -SELECT 'Ace', 'Dog', 'Australian Shepherd', 1, 'Available', 555.00, storeId FROM storeLocation WHERE storeName = 'Downtown Branch'; - --- Insert adopted pets linked to customers -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId) -SELECT 'Shadow', 'Dog', 'Labrador', 3, 'Adopted', 500.00, customerId FROM customer WHERE email = 'alex@gmail.com' UNION ALL -SELECT 'Kitty', 'Cat', 'Persian', 2, 'Adopted', 320.00, customerId FROM customer WHERE email = 'emily@gmail.com' UNION ALL -SELECT 'Bruno', 'Dog', 'Rottweiler', 4, 'Adopted', 580.00, customerId FROM customer WHERE email = 'james@gmail.com' UNION ALL -SELECT 'Snowball', 'Cat', 'Turkish Angora', 1, 'Adopted', 390.00, customerId FROM customer WHERE email = 'olivia@gmail.com' UNION ALL -SELECT 'Zeus', 'Dog', 'Husky', 3, 'Adopted', 640.00, customerId FROM customer WHERE email = 'william@gmail.com'; - --- Insert owned pets linked to customers -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId) -SELECT 'Biscuit', 'Dog', 'Beagle', 2, 'Owned', 0.00, customerId FROM customer WHERE email = 'sophia@gmail.com' UNION ALL -SELECT 'Patches', 'Cat', 'Calico', 5, 'Owned', 0.00, customerId FROM customer WHERE email = 'noah@gmail.com' UNION ALL -SELECT 'Scout', 'Dog', 'Border Collie', 3, 'Owned', 0.00, customerId FROM customer WHERE email = 'mia@gmail.com' UNION ALL -SELECT 'Mittens', 'Cat', 'Domestic Short', 4, 'Owned', 0.00, customerId FROM customer WHERE email = 'ethan@gmail.com' UNION ALL -SELECT 'Thor', 'Dog', 'German Shepherd', 2, 'Owned', 0.00, customerId FROM customer WHERE email = 'chloe@gmail.com'; - --- Insert adoptions for the adopted pets -INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus) -SELECT p.petId, p.customerId, - (SELECT e.employeeId FROM employee e JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE AND u.role = 'STAFF' ORDER BY e.employeeId LIMIT 1), - '2026-01-10', 'Completed' -FROM pet p WHERE p.petName = 'Shadow' AND p.petStatus = 'Adopted'; - -INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus) -SELECT p.petId, p.customerId, - (SELECT e.employeeId FROM employee e JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE AND u.role = 'STAFF' ORDER BY e.employeeId LIMIT 1), - '2026-01-18', 'Completed' -FROM pet p WHERE p.petName = 'Kitty' AND p.petStatus = 'Adopted'; - -INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus) -SELECT p.petId, p.customerId, - (SELECT e.employeeId FROM employee e JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE AND u.role = 'STAFF' ORDER BY e.employeeId LIMIT 1), - '2026-02-03', 'Completed' -FROM pet p WHERE p.petName = 'Bruno' AND p.petStatus = 'Adopted'; - -INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus) -SELECT p.petId, p.customerId, - (SELECT e.employeeId FROM employee e JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE AND u.role = 'STAFF' ORDER BY e.employeeId LIMIT 1), - '2026-02-14', 'Completed' -FROM pet p WHERE p.petName = 'Snowball' AND p.petStatus = 'Adopted'; - -INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus) -SELECT p.petId, p.customerId, - (SELECT e.employeeId FROM employee e JOIN users u ON u.id = e.user_id - WHERE e.isActive = TRUE AND u.role = 'STAFF' ORDER BY e.employeeId LIMIT 1), - '2026-02-21', 'Completed' -FROM pet p WHERE p.petName = 'Zeus' AND p.petStatus = 'Adopted'; - --- Insert customer_pet entries -INSERT INTO customer_pet (customer_id, pet_name, species, breed) -SELECT customerId, 'Rex', 'Dog', 'German Shepherd' FROM customer WHERE email = 'alex@gmail.com' UNION ALL -SELECT customerId, 'Whiskers', 'Cat', 'Tabby' FROM customer WHERE email = 'emily@gmail.com' UNION ALL -SELECT customerId, 'Goldie', 'Dog', 'Golden Retriever' FROM customer WHERE email = 'james@gmail.com' UNION ALL -SELECT customerId, 'Midnight', 'Cat', 'Black' FROM customer WHERE email = 'olivia@gmail.com' UNION ALL -SELECT customerId, 'Storm', 'Dog', 'Husky' FROM customer WHERE email = 'william@gmail.com' UNION ALL -SELECT customerId, 'Peanut', 'Dog', 'Poodle' FROM customer WHERE email = 'sophia@gmail.com' UNION ALL -SELECT customerId, 'Snowball', 'Cat', 'Persian' FROM customer WHERE email = 'noah@gmail.com' UNION ALL -SELECT customerId, 'Duke', 'Dog', 'Labrador' FROM customer WHERE email = 'mia@gmail.com' UNION ALL -SELECT customerId, 'Luna', 'Cat', 'Siamese' FROM customer WHERE email = 'ethan@gmail.com' UNION ALL -SELECT customerId, 'Buster', 'Dog', 'Beagle' FROM customer WHERE email = 'chloe@gmail.com' UNION ALL -SELECT customerId, 'Daisy', 'Dog', 'Corgi' FROM customer WHERE email = 'lucas@gmail.com' UNION ALL -SELECT customerId, 'Cleo', 'Cat', 'Ragdoll' FROM customer WHERE email = 'lily@gmail.com'; - --- Helper function or complex query to seed appointments robustly --- For simplicity and robustness, I will use individual inserts for the first few and a pattern for the rest -INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus, storeId, employeeId) -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'alex@gmail.com'), '2026-01-10', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'emily@gmail.com'), '2026-01-10', '11:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'james@gmail.com'), '2026-01-17', '09:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'olivia@gmail.com'), '2026-01-17', '14:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Teeth Cleaning'), (SELECT customerId FROM customer WHERE email = 'william@gmail.com'), '2026-01-24', '10:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'sophia@gmail.com'), '2026-01-24', '13:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'noah@gmail.com'), '2026-02-07', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'mia@gmail.com'), '2026-02-07', '11:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'ethan@gmail.com'), '2026-01-11', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'chloe@gmail.com'), '2026-01-11', '11:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'lucas@gmail.com'), '2026-01-18', '10:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'lily@gmail.com'), '2026-01-18', '13:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Teeth Cleaning'), (SELECT customerId FROM customer WHERE email = 'alex@gmail.com'), '2026-02-01', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'emily@gmail.com'), '2026-02-01', '14:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'james@gmail.com'), '2026-02-08', '10:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'olivia@gmail.com'), '2026-02-08', '13:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'william@gmail.com'), '2026-01-12', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Teeth Cleaning'), (SELECT customerId FROM customer WHERE email = 'sophia@gmail.com'), '2026-01-12', '11:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'noah@gmail.com'), '2026-01-19', '09:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'mia@gmail.com'), '2026-01-19', '14:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'ethan@gmail.com'), '2026-02-09', '10:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'chloe@gmail.com'), '2026-02-09', '13:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'lucas@gmail.com'), '2026-01-13', '09:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'North Branch'), (SELECT employeeId FROM employee WHERE email = 'michael@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'lily@gmail.com'), '2026-01-13', '11:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'North Branch'), (SELECT employeeId FROM employee WHERE email = 'michael@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'alex@gmail.com'), '2026-02-10', '09:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'North Branch'), (SELECT employeeId FROM employee WHERE email = 'michael@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'emily@gmail.com'), '2026-02-10', '13:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'North Branch'), (SELECT employeeId FROM employee WHERE email = 'michael@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'james@gmail.com'), '2026-01-14', '10:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'West Side Store'), (SELECT employeeId FROM employee WHERE email = 'lisa@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'olivia@gmail.com'), '2026-01-14', '13:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'West Side Store'), (SELECT employeeId FROM employee WHERE email = 'lisa@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'william@gmail.com'), '2026-02-11', '10:00:00', 'Missed', (SELECT storeId FROM storeLocation WHERE storeName = 'West Side Store'), (SELECT employeeId FROM employee WHERE email = 'lisa@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'sophia@gmail.com'), '2026-02-11', '14:00:00', 'Completed', (SELECT storeId FROM storeLocation WHERE storeName = 'West Side Store'), (SELECT employeeId FROM employee WHERE email = 'lisa@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Pet Grooming'), (SELECT customerId FROM customer WHERE email = 'noah@gmail.com'), '2026-04-15', '09:00:00', 'Booked', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'john@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Nail Trimming'), (SELECT customerId FROM customer WHERE email = 'mia@gmail.com'), '2026-04-15', '11:00:00', 'Booked', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'sara@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Bath and Brush'), (SELECT customerId FROM customer WHERE email = 'ethan@gmail.com'), '2026-04-16', '10:00:00', 'Booked', (SELECT storeId FROM storeLocation WHERE storeName = 'Downtown Branch'), (SELECT employeeId FROM employee WHERE email = 'david@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Veterinary Checkup'), (SELECT customerId FROM customer WHERE email = 'chloe@gmail.com'), '2026-04-17', '09:00:00', 'Booked', (SELECT storeId FROM storeLocation WHERE storeName = 'North Branch'), (SELECT employeeId FROM employee WHERE email = 'michael@petshop.com') UNION ALL -SELECT (SELECT serviceId FROM service WHERE serviceName = 'Teeth Cleaning'), (SELECT customerId FROM customer WHERE email = 'lucas@gmail.com'), '2026-04-18', '14:00:00', 'Booked', (SELECT storeId FROM storeLocation WHERE storeName = 'West Side Store'), (SELECT employeeId FROM employee WHERE email = 'lisa@petshop.com'); - --- Re-linking appointments to customer pets using a slightly more robust join --- This still assumes appointments and customer_pets were inserted in a specific order, --- but at least it uses current IDs from the database. -INSERT INTO appointment_customer_pet (appointment_id, customer_pet_id) -SELECT a.appointmentId, cp.customer_pet_id -FROM ( - SELECT appointmentId, ROW_NUMBER() OVER (ORDER BY appointmentId) as row_num - FROM appointment - WHERE appointmentId > (SELECT COALESCE(MAX(appointmentId), 0) FROM (SELECT appointmentId FROM appointment LIMIT 5) t) -) a -JOIN ( - SELECT customer_pet_id, ROW_NUMBER() OVER (ORDER BY customer_pet_id) as row_num - FROM customer_pet -) cp ON ((a.row_num - 1) % 12) + 1 = cp.row_num; diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql deleted file mode 100644 index 5e8d3fb6..00000000 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ /dev/null @@ -1,205 +0,0 @@ --- Insert Sample Data - -INSERT INTO storeLocation (storeName, address, phone, email) -VALUES -('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), -('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), -('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), -('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), -('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); - -INSERT INTO employee (firstName, lastName, email, phone, role, isActive) -VALUES -('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), -('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), -('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), -('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), -('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), -('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); - -INSERT INTO employeeStore (employeeId, storeId) -VALUES -(1, 1), -(2, 1), -(2, 2), -(3, 2), -(4, 3), -(5, 1), -(5, 4), -(6, 5); - -INSERT INTO customer (firstName, lastName, email, phone) -VALUES -('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), -('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), -('James', 'Wilson', 'james@gmail.com', '888-999-0000'), -('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), -('William', 'Anderson', 'william@gmail.com', '000-111-2222'), -('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); - -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) -VALUES -('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), -('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), -('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), -('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), -('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), -('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); - -INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) -VALUES -(1, 1, '2026-01-15', 'Completed'), -(4, 3, '2026-01-20', 'Completed'), -(2, 2, '2026-01-25', 'Pending'), -(5, 4, '2026-02-01', 'Completed'), -(6, 5, '2026-02-02', 'Pending'); - -INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) -VALUES -('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), -('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), -('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), -('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), -('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); - -INSERT INTO category (categoryName, categoryType) -VALUES -('Dog Food', 'Product'), -('Cat Toys', 'Product'), -('Bird Supplies', 'Product'), -('Aquarium', 'Product'), -('Small Animals', 'Product'); - -INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) -VALUES -('Premium Dog Food', 50.00, 1, 'High quality dog food'), -('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), -('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), -('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), -('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), -('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); - -INSERT INTO productSupplier (supId, prodId, cost) -VALUES -(1, 1, 35.00), -(1, 2, 6.50), -(2, 2, 7.00), -(3, 3, 90.00), -(3, 4, 60.00), -(4, 5, 10.00), -(5, 6, 18.00), -(1, 6, 17.50); - -INSERT INTO inventory (prodId, quantity) -VALUES -(1, 100), -(2, 200), -(3, 50), -(4, 30), -(5, 150), -(6, 75); - -INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) -VALUES -('Pet Grooming', 'Full grooming service', 60, 40.00), -('Nail Trimming', 'Quick nail trim', 15, 10.00), -('Bath and Brush', 'Bathing and brushing service', 45, 30.00), -('Veterinary Checkup', 'Complete health examination', 30, 75.00), -('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); - -INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) -VALUES -(1, 2, '2026-02-01', '10:30:00', 'Booked'), -(2, 1, '2026-02-03', '14:00:00', 'Booked'), -(3, 3, '2026-02-05', '09:00:00', 'Completed'), -(4, 4, '2026-02-07', '11:30:00', 'Booked'), -(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); - -INSERT INTO appointmentPet (appointmentId, petId) -VALUES -(1, 2), -(2, 1), -(3, 3), -(4, 5), -(5, 6); - -INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId) -VALUES -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1, 1), -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1, 2), -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2, 3), -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1, 1), -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3, 2), -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2, NULL), -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4, 4), -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1, NULL), -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3, 1), -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1, 3), -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2, NULL), -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1, 2), -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4, NULL), -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2, 1), -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3, NULL), -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1, 4), -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3, NULL), -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1, 2), -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4, NULL), -(NOW(), 95.00, 'Card', 1, 1, 1); - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES -(1, 1, 2, 50.00), -(1, 6, 1, 25.00), -(2, 3, 1, 120.00), -(2, 4, 1, 80.00), -(3, 2, 3, 10.00), -(3, 5, 2, 15.00), -(4, 1, 3, 50.00), -(5, 4, 1, 80.00), -(6, 2, 4, 10.00), -(6, 5, 1, 15.00), -(6, 6, 1, 25.00), -(6, 1, 1, 50.00), -(7, 3, 2, 120.00), -(8, 1, 1, 50.00), -(8, 2, 3, 10.00), -(9, 1, 3, 50.00), -(9, 6, 1, 25.00), -(10, 3, 1, 120.00), -(11, 5, 1, 15.00), -(11, 2, 3, 10.00), -(12, 4, 2, 80.00), -(13, 6, 4, 25.00), -(14, 1, 1, 50.00), -(15, 2, 2, 10.00), -(15, 5, 1, 15.00), -(15, 6, 2, 25.00), -(16, 3, 1, 120.00), -(16, 4, 1, 80.00), -(17, 4, 1, 80.00), -(17, 1, 1, 50.00), -(17, 6, 1, 25.00), -(18, 6, 2, 25.00), -(18, 2, 2, 10.00), -(18, 5, 1, 15.00), -(19, 1, 2, 50.00), -(19, 6, 2, 25.00), -(20, 2, 5, 10.00), -(20, 5, 3, 15.00); - -INSERT INTO purchaseOrder (supId, orderDate, status) -VALUES -(1, '2025-01-15', 'Delivered'), -(2, '2025-01-20', 'Pending'), -(3, '2025-02-01', 'Delivered'), -(4, '2025-02-10', 'In Transit'), -(1, '2025-02-15', 'Pending'); - -INSERT INTO activityLog (employeeId, activity) -VALUES -(1, 'Created new sale'), -(2, 'Booked appointment'), -(3, 'Completed grooming service'), -(4, 'Processed inventory order'), -(5, 'Conducted health checkup'), -(1, 'Updated customer information'); diff --git a/backend/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql b/backend/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql deleted file mode 100644 index 2e65bc98..00000000 --- a/backend/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql +++ /dev/null @@ -1,19 +0,0 @@ -ALTER TABLE appointment - ADD COLUMN storeId BIGINT NULL AFTER customerId; - -UPDATE appointment -SET storeId = 1 -WHERE storeId IS NULL; - -ALTER TABLE appointment - MODIFY COLUMN storeId BIGINT NOT NULL, - ADD CONSTRAINT fk_appointment_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId); - -DELETE es1 -FROM employeeStore es1 -JOIN employeeStore es2 - ON es1.employeeId = es2.employeeId - AND es1.storeId > es2.storeId; - -ALTER TABLE employeeStore - ADD CONSTRAINT uk_employeeStore_employee UNIQUE (employeeId); diff --git a/backend/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql b/backend/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql deleted file mode 100644 index 271c0e72..00000000 --- a/backend/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE conversation - ADD COLUMN mode VARCHAR(20) NOT NULL DEFAULT 'AUTOMATED' AFTER status, - ADD COLUMN humanRequestedAt TIMESTAMP NULL AFTER mode; - -UPDATE conversation -SET mode = CASE - WHEN staffId IS NULL THEN 'AUTOMATED' - ELSE 'HUMAN' -END; diff --git a/backend/src/main/resources/db/migration/V5__user_token_version.sql b/backend/src/main/resources/db/migration/V5__user_token_version.sql deleted file mode 100644 index 455c4bc8..00000000 --- a/backend/src/main/resources/db/migration/V5__user_token_version.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users - ADD COLUMN tokenVersion INT NOT NULL DEFAULT 0 AFTER active; diff --git a/backend/src/main/resources/db/migration/V6__user_phone.sql b/backend/src/main/resources/db/migration/V6__user_phone.sql deleted file mode 100644 index 95478fdc..00000000 --- a/backend/src/main/resources/db/migration/V6__user_phone.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE users - ADD COLUMN phone VARCHAR(20) NULL AFTER fullName; - -UPDATE users u -LEFT JOIN customer c ON c.user_id = u.id -LEFT JOIN employee e ON e.user_id = u.id -SET u.phone = COALESCE(NULLIF(c.phone, ''), NULLIF(e.phone, ''), u.phone) -WHERE u.phone IS NULL OR u.phone = ''; diff --git a/backend/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql b/backend/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql deleted file mode 100644 index fa922a82..00000000 --- a/backend/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql +++ /dev/null @@ -1,11 +0,0 @@ -UPDATE users u -LEFT JOIN customer c ON c.user_id = u.id -LEFT JOIN employee e ON e.user_id = u.id -SET u.phone = COALESCE(NULLIF(u.phone, ''), NULLIF(c.phone, ''), NULLIF(e.phone, '')) -WHERE u.phone IS NULL OR u.phone = ''; - -ALTER TABLE customer - DROP COLUMN phone; - -ALTER TABLE employee - DROP COLUMN phone; diff --git a/backend/src/main/resources/db/migration/V8__pet_product_image_urls.sql b/backend/src/main/resources/db/migration/V8__pet_product_image_urls.sql deleted file mode 100644 index a4c98248..00000000 --- a/backend/src/main/resources/db/migration/V8__pet_product_image_urls.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE pet - ADD COLUMN imageUrl VARCHAR(255) NULL; - -ALTER TABLE product - ADD COLUMN imageUrl VARCHAR(255) NULL; diff --git a/backend/src/main/resources/db/migration/V9__customer_pet.sql b/backend/src/main/resources/db/migration/V9__customer_pet.sql deleted file mode 100644 index 0981bca2..00000000 --- a/backend/src/main/resources/db/migration/V9__customer_pet.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS customer_pet ( - customer_pet_id BIGINT AUTO_INCREMENT PRIMARY KEY, - customer_id BIGINT NOT NULL, - pet_name VARCHAR(50) NOT NULL, - species VARCHAR(50) NOT NULL, - breed VARCHAR(50) NULL, - image_url VARCHAR(255) NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (customer_id) REFERENCES customer(customerId) -); diff --git a/backend/src/main/resources/dev/final-target/final_target_schema.sql b/backend/src/main/resources/dev/final-target/final_target_schema.sql new file mode 100644 index 00000000..9b8d279d --- /dev/null +++ b/backend/src/main/resources/dev/final-target/final_target_schema.sql @@ -0,0 +1,343 @@ +CREATE DATABASE IF NOT EXISTS Petstoredb_target CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE Petstoredb_target; + +CREATE TABLE IF NOT EXISTS storeLocation ( + storeId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL, + imageUrl VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NULL UNIQUE, + password VARCHAR(255) NULL, + email VARCHAR(100) NULL UNIQUE, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + fullName VARCHAR(100) NULL, + phone VARCHAR(20) NULL, + avatarUrl VARCHAR(255) NULL, + role VARCHAR(20) NOT NULL, + staffRole VARCHAR(50) NULL, + primaryStoreId BIGINT NULL, + loyaltyPoints INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT TRUE, + 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 +); + +CREATE TABLE IF NOT EXISTS supplier ( + supId BIGINT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS category ( + categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uq_category_name_type UNIQUE (categoryName, categoryType) +); + +CREATE TABLE IF NOT EXISTS service ( + serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT NULL, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS service_species ( + serviceId BIGINT NOT NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS product ( + prodId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId BIGINT NOT NULL, + prodDesc TEXT NULL, + imageUrl VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_product_category FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE IF NOT EXISTS inventory ( + inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + 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) +); + +CREATE TABLE IF NOT EXISTS productSupplier ( + supId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (supId, prodId), + CONSTRAINT fk_product_supplier_supplier FOREIGN KEY (supId) REFERENCES supplier(supId), + CONSTRAINT fk_product_supplier_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS purchaseOrder ( + purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, + 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), + CONSTRAINT fk_purchase_order_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE IF NOT EXISTS coupon ( + couponId BIGINT AUTO_INCREMENT PRIMARY KEY, + couponCode VARCHAR(50) NOT NULL, + discountType VARCHAR(20) NOT NULL, + discountValue DECIMAL(10, 2) NOT NULL, + minOrderAmount DECIMAL(10, 2) NULL, + active BOOLEAN NOT NULL DEFAULT TRUE, + startsAt DATETIME NULL, + endsAt DATETIME NULL, + usageLimit INT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uq_coupon_code UNIQUE (couponCode) +); + +CREATE TABLE IF NOT EXISTS pet ( + petId BIGINT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NULL, + petAge INT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NULL, + imageUrl VARCHAR(255) NULL, + ownerUserId BIGINT NULL, + storeId BIGINT NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS appointment ( + appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT NOT NULL, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + employeeId BIGINT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + 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_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) +); + +CREATE TABLE IF NOT EXISTS adoption ( + adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + employeeId BIGINT NOT NULL, + sourceStoreId BIGINT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + 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) +); + +CREATE TABLE IF NOT EXISTS cart ( + cartId BIGINT AUTO_INCREMENT PRIMARY KEY, + userId BIGINT NOT NULL, + storeId BIGINT NULL, + couponId BIGINT NULL, + cartStatus VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', + subtotalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + discountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + totalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + 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 +); + +CREATE TABLE IF NOT EXISTS cart_item ( + cartItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + cartId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_cart_item_cart FOREIGN KEY (cartId) REFERENCES cart(cartId) ON DELETE CASCADE, + CONSTRAINT fk_cart_item_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS sale ( + saleId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + customerId BIGINT NULL, + isRefund BOOLEAN NOT NULL DEFAULT FALSE, + originalSaleId BIGINT NULL, + channel VARCHAR(20) NOT NULL DEFAULT 'IN_STORE', + cartId BIGINT NULL, + couponId BIGINT NULL, + subtotalAmount DECIMAL(10, 2) NULL, + couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + pointsEarned INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id), + CONSTRAINT fk_sale_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + 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 +); + +CREATE TABLE IF NOT EXISTS saleItem ( + saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_sale_item_sale FOREIGN KEY (saleId) REFERENCES sale(saleId) ON DELETE CASCADE, + CONSTRAINT fk_sale_item_product FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS refund ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + reason VARCHAR(500) NOT NULL, + status VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_refund_sale FOREIGN KEY (saleId) REFERENCES sale(saleId), + CONSTRAINT fk_refund_customer FOREIGN KEY (customerId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS refund_item ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + refund_id BIGINT NOT NULL, + prod_id BIGINT NOT NULL, + quantity INT NOT NULL, + unit_price DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_refund_item_refund FOREIGN KEY (refund_id) REFERENCES refund(id) ON DELETE CASCADE, + CONSTRAINT fk_refund_item_product FOREIGN KEY (prod_id) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS conversation ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customerId BIGINT NOT NULL, + staffId BIGINT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'OPEN', + mode VARCHAR(20) NOT NULL DEFAULT 'AUTOMATED', + humanRequestedAt TIMESTAMP NULL, + 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 +); + +CREATE TABLE IF NOT EXISTS message ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + conversationId BIGINT NOT NULL, + senderId BIGINT NOT NULL, + content TEXT NULL, + attachmentUrl VARCHAR(255) NULL, + attachmentName VARCHAR(255) NULL, + attachmentMimeType VARCHAR(100) NULL, + attachmentSizeBytes BIGINT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + isRead BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT fk_message_conversation FOREIGN KEY (conversationId) REFERENCES conversation(id) ON DELETE CASCADE, + CONSTRAINT fk_message_sender FOREIGN KEY (senderId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS activityLog ( + logId BIGINT AUTO_INCREMENT PRIMARY KEY, + userId BIGINT NOT NULL, + storeId BIGINT NULL, + 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 +); + +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); diff --git a/backend/src/main/resources/dev/final-target/final_target_seed.sql b/backend/src/main/resources/dev/final-target/final_target_seed.sql new file mode 100644 index 00000000..f1db3380 --- /dev/null +++ b/backend/src/main/resources/dev/final-target/final_target_seed.sql @@ -0,0 +1,1861 @@ +USE Petstoredb_target; + +SET FOREIGN_KEY_CHECKS = 0; + +DELETE FROM activityLog; +DELETE FROM message; +DELETE FROM conversation; +DELETE FROM refund_item; +DELETE FROM refund; +DELETE FROM saleItem; +DELETE FROM sale; +DELETE FROM cart_item; +DELETE FROM cart; +DELETE FROM adoption; +DELETE FROM appointment; +DELETE FROM pet; +DELETE FROM coupon; +DELETE FROM purchaseOrder; +DELETE FROM inventory; +DELETE FROM productSupplier; +DELETE FROM product; +DELETE FROM service_species; +DELETE FROM service; +DELETE FROM category; +DELETE FROM supplier; +DELETE FROM users; +DELETE FROM storeLocation; +ALTER TABLE storeLocation AUTO_INCREMENT = 1; +ALTER TABLE users AUTO_INCREMENT = 1; +ALTER TABLE supplier AUTO_INCREMENT = 1; +ALTER TABLE category AUTO_INCREMENT = 1; +ALTER TABLE service AUTO_INCREMENT = 1; +ALTER TABLE product AUTO_INCREMENT = 1; +ALTER TABLE inventory AUTO_INCREMENT = 1; +ALTER TABLE purchaseOrder AUTO_INCREMENT = 1; +ALTER TABLE coupon AUTO_INCREMENT = 1; +ALTER TABLE pet AUTO_INCREMENT = 1; +ALTER TABLE appointment AUTO_INCREMENT = 1; +ALTER TABLE adoption AUTO_INCREMENT = 1; +ALTER TABLE cart AUTO_INCREMENT = 1; +ALTER TABLE cart_item AUTO_INCREMENT = 1; +ALTER TABLE sale AUTO_INCREMENT = 1; +ALTER TABLE saleItem AUTO_INCREMENT = 1; +ALTER TABLE refund AUTO_INCREMENT = 1; +ALTER TABLE refund_item AUTO_INCREMENT = 1; +ALTER TABLE conversation AUTO_INCREMENT = 1; +ALTER TABLE message AUTO_INCREMENT = 1; +ALTER TABLE activityLog AUTO_INCREMENT = 1; +SET FOREIGN_KEY_CHECKS = 1; + +INSERT INTO storeLocation (storeId, storeName, address, phone, email, imageUrl) VALUES +(1, 'Downtown Branch', '123 Main St, Calgary, AB', '403-555-0101', 'downtown@petshop.com', 'https://images.petshop.local/stores/downtown.webp'), +(2, 'North Branch', '456 North Ave, Calgary, AB', '403-555-0102', 'north@petshop.com', 'https://images.petshop.local/stores/north.webp'), +(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), +(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', 1, 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', 2, 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', 3, 15, 1, 0), +(18, 'alex.wilson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wilson@gmail.com', 'Alex', 'Wilson', 'Alex Wilson', '403-730-0018', 'https://images.petshop.local/users/018.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(19, 'alex.martinez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.martinez@gmail.com', 'Alex', 'Martinez', 'Alex Martinez', '403-730-0019', 'https://images.petshop.local/users/019.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0), +(20, 'alex.anderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.anderson@gmail.com', 'Alex', 'Anderson', 'Alex Anderson', '403-730-0020', 'https://images.petshop.local/users/020.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0), +(21, 'alex.taylor', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.taylor@gmail.com', 'Alex', 'Taylor', 'Alex Taylor', '403-730-0021', 'https://images.petshop.local/users/021.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0), +(22, 'alex.parker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.parker@gmail.com', 'Alex', 'Parker', 'Alex Parker', '403-730-0022', 'https://images.petshop.local/users/022.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0), +(23, 'alex.evans', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.evans@gmail.com', 'Alex', 'Evans', 'Alex Evans', '403-730-0023', 'https://images.petshop.local/users/023.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0), +(24, 'alex.scott', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.scott@gmail.com', 'Alex', 'Scott', 'Alex Scott', '403-730-0024', 'https://images.petshop.local/users/024.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0), +(25, 'alex.adams', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.adams@gmail.com', 'Alex', 'Adams', 'Alex Adams', '403-730-0025', 'https://images.petshop.local/users/025.webp', 'CUSTOMER', 'CUSTOMER', 2, 8, 1, 0), +(26, 'alex.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.baker@gmail.com', 'Alex', 'Baker', 'Alex Baker', '403-730-0026', 'https://images.petshop.local/users/026.webp', 'CUSTOMER', 'CUSTOMER', 3, 29, 1, 0), +(27, 'alex.hall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hall@gmail.com', 'Alex', 'Hall', 'Alex Hall', '403-730-0027', 'https://images.petshop.local/users/027.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0), +(28, 'alex.rivera', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.rivera@gmail.com', 'Alex', 'Rivera', 'Alex Rivera', '403-730-0028', 'https://images.petshop.local/users/028.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0), +(29, 'alex.mitchell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.mitchell@gmail.com', 'Alex', 'Mitchell', 'Alex Mitchell', '403-730-0029', 'https://images.petshop.local/users/029.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0), +(30, 'alex.collins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.collins@gmail.com', 'Alex', 'Collins', 'Alex Collins', '403-730-0030', 'https://images.petshop.local/users/030.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0), +(31, 'alex.morris', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.morris@gmail.com', 'Alex', 'Morris', 'Alex Morris', '403-730-0031', 'https://images.petshop.local/users/031.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0), +(32, 'alex.cook', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cook@gmail.com', 'Alex', 'Cook', 'Alex Cook', '403-730-0032', 'https://images.petshop.local/users/032.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0), +(33, 'alex.bell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bell@gmail.com', 'Alex', 'Bell', 'Alex Bell', '403-730-0033', 'https://images.petshop.local/users/033.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(34, 'alex.reed', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.reed@gmail.com', 'Alex', 'Reed', 'Alex Reed', '403-730-0034', 'https://images.petshop.local/users/034.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0), +(35, 'alex.murphy', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murphy@gmail.com', 'Alex', 'Murphy', 'Alex Murphy', '403-730-0035', 'https://images.petshop.local/users/035.webp', 'CUSTOMER', 'CUSTOMER', 3, 31, 1, 0), +(36, 'alex.bailey', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bailey@gmail.com', 'Alex', 'Bailey', 'Alex Bailey', '403-730-0036', 'https://images.petshop.local/users/036.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0), +(37, 'alex.cooper', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cooper@gmail.com', 'Alex', 'Cooper', 'Alex Cooper', '403-730-0037', 'https://images.petshop.local/users/037.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0), +(38, 'alex.richardson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.richardson@gmail.com', 'Alex', 'Richardson', 'Alex Richardson', '403-730-0038', 'https://images.petshop.local/users/038.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0), +(39, 'alex.cox', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cox@gmail.com', 'Alex', 'Cox', 'Alex Cox', '403-730-0039', 'https://images.petshop.local/users/039.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0), +(40, 'alex.howard', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.howard@gmail.com', 'Alex', 'Howard', 'Alex Howard', '403-730-0040', 'https://images.petshop.local/users/040.webp', 'CUSTOMER', 'CUSTOMER', 2, 12, 1, 0), +(41, 'alex.ward', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ward@gmail.com', 'Alex', 'Ward', 'Alex Ward', '403-730-0041', 'https://images.petshop.local/users/041.webp', 'CUSTOMER', 'CUSTOMER', 3, 18, 1, 0), +(42, 'alex.torres', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.torres@gmail.com', 'Alex', 'Torres', 'Alex Torres', '403-730-0042', 'https://images.petshop.local/users/042.webp', 'CUSTOMER', 'CUSTOMER', 1, 10, 1, 0), +(43, 'alex.peterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.peterson@gmail.com', 'Alex', 'Peterson', 'Alex Peterson', '403-730-0043', 'https://images.petshop.local/users/043.webp', 'CUSTOMER', 'CUSTOMER', 2, 6, 1, 0), +(44, 'alex.gray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gray@gmail.com', 'Alex', 'Gray', 'Alex Gray', '403-730-0044', 'https://images.petshop.local/users/044.webp', 'CUSTOMER', 'CUSTOMER', 3, 11, 1, 0), +(45, 'alex.ramirez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ramirez@gmail.com', 'Alex', 'Ramirez', 'Alex Ramirez', '403-730-0045', 'https://images.petshop.local/users/045.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0), +(46, 'alex.james', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.james@gmail.com', 'Alex', 'James', 'Alex James', '403-730-0046', 'https://images.petshop.local/users/046.webp', 'CUSTOMER', 'CUSTOMER', 2, 28, 1, 0), +(47, 'alex.watson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.watson@gmail.com', 'Alex', 'Watson', 'Alex Watson', '403-730-0047', 'https://images.petshop.local/users/047.webp', 'CUSTOMER', 'CUSTOMER', 3, 8, 1, 0), +(48, 'alex.brooks', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.brooks@gmail.com', 'Alex', 'Brooks', 'Alex Brooks', '403-730-0048', 'https://images.petshop.local/users/048.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(49, 'alex.kelly', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.kelly@gmail.com', 'Alex', 'Kelly', 'Alex Kelly', '403-730-0049', 'https://images.petshop.local/users/049.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0), +(50, 'alex.sanders', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.sanders@gmail.com', 'Alex', 'Sanders', 'Alex Sanders', '403-730-0050', 'https://images.petshop.local/users/050.webp', 'CUSTOMER', 'CUSTOMER', 3, 21, 1, 0), +(51, 'alex.price', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.price@gmail.com', 'Alex', 'Price', 'Alex Price', '403-730-0051', 'https://images.petshop.local/users/051.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0), +(52, 'alex.bennett', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bennett@gmail.com', 'Alex', 'Bennett', 'Alex Bennett', '403-730-0052', 'https://images.petshop.local/users/052.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0), +(53, 'alex.wood', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wood@gmail.com', 'Alex', 'Wood', 'Alex Wood', '403-730-0053', 'https://images.petshop.local/users/053.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0), +(54, 'alex.barnes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.barnes@gmail.com', 'Alex', 'Barnes', 'Alex Barnes', '403-730-0054', 'https://images.petshop.local/users/054.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(55, 'alex.ross', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ross@gmail.com', 'Alex', 'Ross', 'Alex Ross', '403-730-0055', 'https://images.petshop.local/users/055.webp', 'CUSTOMER', 'CUSTOMER', 2, 7, 1, 0), +(56, 'alex.henderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.henderson@gmail.com', 'Alex', 'Henderson', 'Alex Henderson', '403-730-0056', 'https://images.petshop.local/users/056.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0), +(57, 'alex.coleman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.coleman@gmail.com', 'Alex', 'Coleman', 'Alex Coleman', '403-730-0057', 'https://images.petshop.local/users/057.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(58, 'alex.jenkins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.jenkins@gmail.com', 'Alex', 'Jenkins', 'Alex Jenkins', '403-730-0058', 'https://images.petshop.local/users/058.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0), +(59, 'alex.perry', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.perry@gmail.com', 'Alex', 'Perry', 'Alex Perry', '403-730-0059', 'https://images.petshop.local/users/059.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0), +(60, 'alex.powell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.powell@gmail.com', 'Alex', 'Powell', 'Alex Powell', '403-730-0060', 'https://images.petshop.local/users/060.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0), +(61, 'alex.long', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.long@gmail.com', 'Alex', 'Long', 'Alex Long', '403-730-0061', 'https://images.petshop.local/users/061.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0), +(62, 'alex.patterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.patterson@gmail.com', 'Alex', 'Patterson', 'Alex Patterson', '403-730-0062', 'https://images.petshop.local/users/062.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0), +(63, 'alex.hughes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hughes@gmail.com', 'Alex', 'Hughes', 'Alex Hughes', '403-730-0063', 'https://images.petshop.local/users/063.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0), +(64, 'alex.flores', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.flores@gmail.com', 'Alex', 'Flores', 'Alex Flores', '403-730-0064', 'https://images.petshop.local/users/064.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0), +(65, 'alex.washington', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.washington@gmail.com', 'Alex', 'Washington', 'Alex Washington', '403-730-0065', 'https://images.petshop.local/users/065.webp', 'CUSTOMER', 'CUSTOMER', 3, 22, 1, 0), +(66, 'alex.butler', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.butler@gmail.com', 'Alex', 'Butler', 'Alex Butler', '403-730-0066', 'https://images.petshop.local/users/066.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0), +(67, 'alex.simmons', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.simmons@gmail.com', 'Alex', 'Simmons', 'Alex Simmons', '403-730-0067', 'https://images.petshop.local/users/067.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0), +(68, 'alex.foster', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.foster@gmail.com', 'Alex', 'Foster', 'Alex Foster', '403-730-0068', 'https://images.petshop.local/users/068.webp', 'CUSTOMER', 'CUSTOMER', 3, 17, 1, 0), +(69, 'alex.gonzales', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gonzales@gmail.com', 'Alex', 'Gonzales', 'Alex Gonzales', '403-730-0069', 'https://images.petshop.local/users/069.webp', 'CUSTOMER', 'CUSTOMER', 1, 15, 1, 0), +(70, 'alex.bryant', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bryant@gmail.com', 'Alex', 'Bryant', 'Alex Bryant', '403-730-0070', 'https://images.petshop.local/users/070.webp', 'CUSTOMER', 'CUSTOMER', 2, 19, 1, 0), +(71, 'alex.alexander', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.alexander@gmail.com', 'Alex', 'Alexander', 'Alex Alexander', '403-730-0071', 'https://images.petshop.local/users/071.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0), +(72, 'alex.russell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.russell@gmail.com', 'Alex', 'Russell', 'Alex Russell', '403-730-0072', 'https://images.petshop.local/users/072.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0), +(73, 'alex.griffin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.griffin@gmail.com', 'Alex', 'Griffin', 'Alex Griffin', '403-730-0073', 'https://images.petshop.local/users/073.webp', 'CUSTOMER', 'CUSTOMER', 2, 2, 1, 0), +(74, 'alex.diaz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.diaz@gmail.com', 'Alex', 'Diaz', 'Alex Diaz', '403-730-0074', 'https://images.petshop.local/users/074.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0), +(75, 'alex.hayes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hayes@gmail.com', 'Alex', 'Hayes', 'Alex Hayes', '403-730-0075', 'https://images.petshop.local/users/075.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0), +(76, 'alex.myers', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.myers@gmail.com', 'Alex', 'Myers', 'Alex Myers', '403-730-0076', 'https://images.petshop.local/users/076.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0), +(77, 'alex.ford', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ford@gmail.com', 'Alex', 'Ford', 'Alex Ford', '403-730-0077', 'https://images.petshop.local/users/077.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0), +(78, 'alex.hamilton', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hamilton@gmail.com', 'Alex', 'Hamilton', 'Alex Hamilton', '403-730-0078', 'https://images.petshop.local/users/078.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(79, 'alex.graham', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.graham@gmail.com', 'Alex', 'Graham', 'Alex Graham', '403-730-0079', 'https://images.petshop.local/users/079.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0), +(80, 'alex.sullivan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.sullivan@gmail.com', 'Alex', 'Sullivan', 'Alex Sullivan', '403-730-0080', 'https://images.petshop.local/users/080.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0), +(81, 'alex.wallace', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wallace@gmail.com', 'Alex', 'Wallace', 'Alex Wallace', '403-730-0081', 'https://images.petshop.local/users/081.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0), +(82, 'alex.woods', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.woods@gmail.com', 'Alex', 'Woods', 'Alex Woods', '403-730-0082', 'https://images.petshop.local/users/082.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0), +(83, 'alex.cole', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cole@gmail.com', 'Alex', 'Cole', 'Alex Cole', '403-730-0083', 'https://images.petshop.local/users/083.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0), +(84, 'alex.west', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.west@gmail.com', 'Alex', 'West', 'Alex West', '403-730-0084', 'https://images.petshop.local/users/084.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0), +(85, 'alex.jordan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.jordan@gmail.com', 'Alex', 'Jordan', 'Alex Jordan', '403-730-0085', 'https://images.petshop.local/users/085.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0), +(86, 'alex.owens', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.owens@gmail.com', 'Alex', 'Owens', 'Alex Owens', '403-730-0086', 'https://images.petshop.local/users/086.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0), +(87, 'alex.reynolds', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.reynolds@gmail.com', 'Alex', 'Reynolds', 'Alex Reynolds', '403-730-0087', 'https://images.petshop.local/users/087.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0), +(88, 'alex.fisher', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.fisher@gmail.com', 'Alex', 'Fisher', 'Alex Fisher', '403-730-0088', 'https://images.petshop.local/users/088.webp', 'CUSTOMER', 'CUSTOMER', 2, 11, 1, 0), +(89, 'alex.ellis', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ellis@gmail.com', 'Alex', 'Ellis', 'Alex Ellis', '403-730-0089', 'https://images.petshop.local/users/089.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0), +(90, 'alex.harrison', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.harrison@gmail.com', 'Alex', 'Harrison', 'Alex Harrison', '403-730-0090', 'https://images.petshop.local/users/090.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0), +(91, 'alex.gibson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gibson@gmail.com', 'Alex', 'Gibson', 'Alex Gibson', '403-730-0091', 'https://images.petshop.local/users/091.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0), +(92, 'alex.mcdonald', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.mcdonald@gmail.com', 'Alex', 'Mcdonald', 'Alex Mcdonald', '403-730-0092', 'https://images.petshop.local/users/092.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0), +(93, 'alex.cruz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cruz@gmail.com', 'Alex', 'Cruz', 'Alex Cruz', '403-730-0093', 'https://images.petshop.local/users/093.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0), +(94, 'alex.marshall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.marshall@gmail.com', 'Alex', 'Marshall', 'Alex Marshall', '403-730-0094', 'https://images.petshop.local/users/094.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0), +(95, 'alex.ortiz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ortiz@gmail.com', 'Alex', 'Ortiz', 'Alex Ortiz', '403-730-0095', 'https://images.petshop.local/users/095.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0), +(96, 'alex.gomez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gomez@gmail.com', 'Alex', 'Gomez', 'Alex Gomez', '403-730-0096', 'https://images.petshop.local/users/096.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0), +(97, 'alex.murray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murray@gmail.com', 'Alex', 'Murray', 'Alex Murray', '403-730-0097', 'https://images.petshop.local/users/097.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0), +(98, 'alex.freeman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.freeman@gmail.com', 'Alex', 'Freeman', 'Alex Freeman', '403-730-0098', 'https://images.petshop.local/users/098.webp', 'CUSTOMER', 'CUSTOMER', 3, 0, 1, 0), +(99, 'alex.wells', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wells@gmail.com', 'Alex', 'Wells', 'Alex Wells', '403-730-0099', 'https://images.petshop.local/users/099.webp', 'CUSTOMER', 'CUSTOMER', 1, 0, 1, 0), +(100, 'alex.webb', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.webb@gmail.com', 'Alex', 'Webb', 'Alex Webb', '403-730-0100', 'https://images.petshop.local/users/100.webp', 'CUSTOMER', 'CUSTOMER', 2, 0, 1, 0); + +INSERT INTO supplier (supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) VALUES +(1, 'PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '403-601-1001'), +(2, 'Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '403-601-1002'), +(3, 'Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '403-601-1003'), +(4, 'Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '403-601-1004'), +(5, 'Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '403-601-1005'), +(6, 'Prairie Feeds', 'Lauren', 'Miles', 'hello@prairiefeeds.com', '403-601-1006'), +(7, 'Whisker Works', 'Darren', 'Cole', 'support@whiskerworks.com', '403-601-1007'), +(8, 'AquaLife Traders', 'Sonia', 'Bell', 'service@aqualife.com', '403-601-1008'), +(9, 'Feather & Finch', 'Maya', 'Stone', 'sales@featherfinch.com', '403-601-1009'), +(10, 'Habitat House', 'Riley', 'Ward', 'orders@habitathouse.com', '403-601-1010'), +(11, 'Trail Tails', 'Evan', 'Frost', 'contact@trailtails.com', '403-601-1011'), +(12, 'CalmPaws Health', 'Ivy', 'Brooks', 'care@calmpaws.com', '403-601-1012'); + +INSERT INTO category (categoryId, categoryName, categoryType) VALUES +(1, 'Dog Food', 'Product'), +(2, 'Cat Toys', 'Product'), +(3, 'Bird Supplies', 'Product'), +(4, 'Aquarium', 'Product'), +(5, 'Small Animals', 'Product'), +(6, 'Pet Health', 'Product'), +(7, 'Grooming Essentials', 'Product'), +(8, 'Habitats', 'Product'), +(9, 'Training & Travel', 'Product'), +(10, 'Treats & Chews', 'Product'); + +INSERT INTO service (serviceId, serviceName, serviceDesc, serviceDuration, servicePrice) VALUES +(1, 'Pet Grooming', 'Full grooming service for coat care and hygiene.', 60, 45.00), +(2, 'Nail Trimming', 'Quick nail trim for pets that need routine care.', 15, 12.00), +(3, 'Bath and Brush', 'Bathing and brushing service for shedding control.', 45, 34.00), +(4, 'Veterinary Checkup', 'General wellness check with basic health review.', 30, 80.00), +(5, 'Teeth Cleaning', 'Routine dental cleaning for eligible pets.', 50, 65.00), +(6, 'Wing Clipping', 'Safe wing trim for birds that require it.', 20, 18.00), +(7, 'Beak and Nail Care', 'Light beak and claw maintenance for birds.', 25, 22.00), +(8, 'Aquarium Health Check', 'Fish wellness and habitat consultation appointment.', 25, 28.00); + +INSERT INTO service_species (serviceId, species) VALUES +(1, 'Dog'), +(1, 'Cat'), +(1, 'Rabbit'), +(2, 'Dog'), +(2, 'Cat'), +(2, 'Rabbit'), +(2, 'Guinea Pig'), +(2, 'Hamster'), +(2, 'Bird'), +(3, 'Dog'), +(3, 'Cat'), +(3, 'Rabbit'), +(3, 'Guinea Pig'), +(4, 'Dog'), +(4, 'Cat'), +(4, 'Rabbit'), +(4, 'Bird'), +(4, 'Fish'), +(4, 'Hamster'), +(4, 'Guinea Pig'), +(5, 'Dog'), +(5, 'Cat'), +(5, 'Rabbit'), +(5, 'Guinea Pig'), +(5, 'Hamster'), +(6, 'Bird'), +(7, 'Bird'), +(8, 'Fish'); + +INSERT INTO product (prodId, prodName, prodPrice, categoryId, prodDesc, imageUrl) VALUES +(1, 'Premium Dog Food', 25.09, 1, 'Balanced nutrition for dogs. Premium Dog Food is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/001.webp'), +(2, 'Salmon Kibble Dog', 30.51, 1, 'Balanced nutrition for dogs. Salmon Kibble Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/002.webp'), +(3, 'Chicken Recipe Dog', 35.93, 1, 'Balanced nutrition for dogs. Chicken Recipe Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/003.webp'), +(4, 'Lamb Formula Dog', 41.36, 1, 'Balanced nutrition for dogs. Lamb Formula Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/004.webp'), +(5, 'Grain-Free Blend Dog', 46.78, 1, 'Balanced nutrition for dogs. Grain-Free Blend Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/005.webp'), +(6, 'Senior Dinner Dog', 52.20, 1, 'Balanced nutrition for dogs. Senior Dinner Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/006.webp'), +(7, 'Puppy Meal Dog', 57.62, 1, 'Balanced nutrition for dogs. Puppy Meal Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/007.webp'), +(8, 'Weight Control Bites Dog', 63.05, 1, 'Balanced nutrition for dogs. Weight Control Bites Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/008.webp'), +(9, 'High Energy Mix Dog', 68.47, 1, 'Balanced nutrition for dogs. High Energy Mix Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/009.webp'), +(10, 'Limited Ingredient Cuisine Dog', 73.89, 1, 'Balanced nutrition for dogs. Limited Ingredient Cuisine Dog is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/010.webp'), +(11, 'Catnip Toy', 7.49, 2, 'Playtime toy for cats. Catnip Toy is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/011.webp'), +(12, 'Feather Chaser Cat', 9.71, 2, 'Playtime toy for cats. Feather Chaser Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/012.webp'), +(13, 'Laser Teaser Cat', 11.93, 2, 'Playtime toy for cats. Laser Teaser Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/013.webp'), +(14, 'Tunnel Ball Cat', 14.16, 2, 'Playtime toy for cats. Tunnel Ball Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/014.webp'), +(15, 'Crinkle Set Cat', 16.38, 2, 'Playtime toy for cats. Crinkle Set Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/015.webp'), +(16, 'Wand Roller Cat', 18.60, 2, 'Playtime toy for cats. Wand Roller Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/016.webp'), +(17, 'Interactive Spinner Cat', 20.82, 2, 'Playtime toy for cats. Interactive Spinner Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/017.webp'), +(18, 'Bell Track Cat', 23.05, 2, 'Playtime toy for cats. Bell Track Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/018.webp'), +(19, 'Mouse Spring Cat', 25.27, 2, 'Playtime toy for cats. Mouse Spring Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/019.webp'), +(20, 'Puzzle Bundle Cat', 27.49, 2, 'Playtime toy for cats. Puzzle Bundle Cat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/020.webp'), +(21, 'Perch Bird Kit', 10.89, 3, 'Everyday bird care item. Perch Bird Kit is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/021.webp'), +(22, 'Seed Accessory Bird', 15.25, 3, 'Everyday bird care item. Seed Accessory Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/022.webp'), +(23, 'Mirror Set Bird', 19.60, 3, 'Everyday bird care item. Mirror Set Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/023.webp'), +(24, 'Ladder Pack Bird', 23.96, 3, 'Everyday bird care item. Ladder Pack Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/024.webp'), +(25, 'Bell Supply Bird', 28.31, 3, 'Everyday bird care item. Bell Supply Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/025.webp'), +(26, 'Foraging Refill Bird', 32.67, 3, 'Everyday bird care item. Foraging Refill Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/026.webp'), +(27, 'Treat Stand Bird', 37.02, 3, 'Everyday bird care item. Treat Stand Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/027.webp'), +(28, 'Cuttlebone Mix Bird', 41.38, 3, 'Everyday bird care item. Cuttlebone Mix Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/028.webp'), +(29, 'Carrier Bundle Bird', 45.73, 3, 'Everyday bird care item. Carrier Bundle Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/029.webp'), +(30, 'Bath Support Bird', 50.09, 3, 'Everyday bird care item. Bath Support Bird is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/030.webp'), +(31, 'Nano Aquarium Kit', 21.29, 4, 'Aquarium and fish care supply. Nano Aquarium Kit is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/031.webp'), +(32, 'Glass Accessory Aquarium', 34.00, 4, 'Aquarium and fish care supply. Glass Accessory Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/032.webp'), +(33, 'Filter Supply Aquarium', 46.71, 4, 'Aquarium and fish care supply. Filter Supply Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/033.webp'), +(34, 'Heater Filter Pack Aquarium', 59.42, 4, 'Aquarium and fish care supply. Heater Filter Pack Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/034.webp'), +(35, 'Water Tank Tool', 72.13, 4, 'Aquarium and fish care supply. Water Tank Tool is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/035.webp'), +(36, 'Coral Cleaner Aquarium', 84.85, 4, 'Aquarium and fish care supply. Coral Cleaner Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/036.webp'), +(37, 'Pebble Media Aquarium', 97.56, 4, 'Aquarium and fish care supply. Pebble Media Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/037.webp'), +(38, 'Plant Care Set Aquarium', 110.27, 4, 'Aquarium and fish care supply. Plant Care Set Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/038.webp'), +(39, 'Light Starter Aquarium', 122.98, 4, 'Aquarium and fish care supply. Light Starter Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/039.webp'), +(40, 'Pump System Aquarium', 135.69, 4, 'Aquarium and fish care supply. Pump System Aquarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/040.webp'), +(41, 'Hay Small Pet Kit', 11.04, 5, 'Care product for rabbits, hamsters, and guinea pigs. Hay Small Pet Kit is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/041.webp'), +(42, 'Hideout Supply Small Pet', 16.86, 5, 'Care product for rabbits, hamsters, and guinea pigs. Hideout Supply Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/042.webp'), +(43, 'Chew Care Pack Small Pet', 22.68, 5, 'Care product for rabbits, hamsters, and guinea pigs. Chew Care Pack Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/043.webp'), +(44, 'Wheel Comfort Item Small Pet', 28.51, 5, 'Care product for rabbits, hamsters, and guinea pigs. Wheel Comfort Item Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/044.webp'), +(45, 'Bottle Exercise Toy Small Pet', 34.33, 5, 'Care product for rabbits, hamsters, and guinea pigs. Bottle Exercise Toy Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/045.webp'), +(46, 'Pellet Refill Small Pet', 40.15, 5, 'Care product for rabbits, hamsters, and guinea pigs. Pellet Refill Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/046.webp'), +(47, 'Tunnel Bundle Small Pet', 45.97, 5, 'Care product for rabbits, hamsters, and guinea pigs. Tunnel Bundle Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/047.webp'), +(48, 'Bedding Snack Small Pet', 51.80, 5, 'Care product for rabbits, hamsters, and guinea pigs. Bedding Snack Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/048.webp'), +(49, 'Play Starter Small Pet', 57.62, 5, 'Care product for rabbits, hamsters, and guinea pigs. Play Starter Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/049.webp'), +(50, 'Nest Accessory Small Pet', 63.44, 5, 'Care product for rabbits, hamsters, and guinea pigs. Nest Accessory Small Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/050.webp'), +(51, 'Calming Support Pet', 14.09, 6, 'General health support product. Calming Support Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/051.webp'), +(52, 'Joint Drops Pet', 18.62, 6, 'General health support product. Joint Drops Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/052.webp'), +(53, 'Digestive Chew Pet', 23.16, 6, 'General health support product. Digestive Chew Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/053.webp'), +(54, 'Skin Spray Pet', 27.69, 6, 'General health support product. Skin Spray Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/054.webp'), +(55, 'Ear Kit Pet', 32.22, 6, 'General health support product. Ear Kit Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/055.webp'), +(56, 'Dental Gel Pet', 36.76, 6, 'General health support product. Dental Gel Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/056.webp'), +(57, 'Vitamin Tabs Pet', 41.29, 6, 'General health support product. Vitamin Tabs Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/057.webp'), +(58, 'Recovery Wash Pet', 45.82, 6, 'General health support product. Recovery Wash Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/058.webp'), +(59, 'Hydration Powder Pet', 50.36, 6, 'General health support product. Hydration Powder Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/059.webp'), +(60, 'Wellness Formula Pet', 54.89, 6, 'General health support product. Wellness Formula Pet is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/060.webp'), +(61, 'Gentle Shampoo', 10.69, 7, 'Grooming essential for regular care. Gentle Shampoo is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/061.webp'), +(62, 'Deep Clean Brush', 13.09, 7, 'Grooming essential for regular care. Deep Clean Brush is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/062.webp'), +(63, 'Oatmeal Wipe', 15.49, 7, 'Grooming essential for regular care. Oatmeal Wipe is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/063.webp'), +(64, 'Deodorizing Conditioner', 17.89, 7, 'Grooming essential for regular care. Deodorizing Conditioner is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/064.webp'), +(65, 'Detangling Comb', 20.29, 7, 'Grooming essential for regular care. Detangling Comb is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/065.webp'), +(66, 'Soft Coat Mist', 22.69, 7, 'Grooming essential for regular care. Soft Coat Mist is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/066.webp'), +(67, 'Paw Foam', 25.09, 7, 'Grooming essential for regular care. Paw Foam is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/067.webp'), +(68, 'Fresh Towel', 27.49, 7, 'Grooming essential for regular care. Fresh Towel is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/068.webp'), +(69, 'Silky Rinse', 29.89, 7, 'Grooming essential for regular care. Silky Rinse is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/069.webp'), +(70, 'Quick Dry Balm', 32.29, 7, 'Grooming essential for regular care. Quick Dry Balm is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/070.webp'), +(71, 'Compact Habitat', 37.99, 8, 'Habitat or enclosure for pet comfort. Compact Habitat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/071.webp'), +(72, 'Deluxe Crate', 53.99, 8, 'Habitat or enclosure for pet comfort. Deluxe Crate is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/072.webp'), +(73, 'Travel Carrier', 69.99, 8, 'Habitat or enclosure for pet comfort. Travel Carrier is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/073.webp'), +(74, 'Corner Enclosure', 85.99, 8, 'Habitat or enclosure for pet comfort. Corner Enclosure is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/074.webp'), +(75, 'Stacked Cage', 101.99, 8, 'Habitat or enclosure for pet comfort. Stacked Cage is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/075.webp'), +(76, 'Starter House Habitat', 117.99, 8, 'Habitat or enclosure for pet comfort. Starter House Habitat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/076.webp'), +(77, 'Ventilated Stand Habitat', 133.99, 8, 'Habitat or enclosure for pet comfort. Ventilated Stand Habitat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/077.webp'), +(78, 'Eco Loft Habitat', 149.99, 8, 'Habitat or enclosure for pet comfort. Eco Loft Habitat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/078.webp'), +(79, 'Secure Playpen Habitat', 165.99, 8, 'Habitat or enclosure for pet comfort. Secure Playpen Habitat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/079.webp'), +(80, 'Open-Air Terrarium', 181.99, 8, 'Habitat or enclosure for pet comfort. Open-Air Terrarium is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/080.webp'), +(81, 'Clicker Kit', 17.99, 9, 'Training and travel accessory. Clicker Kit is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/081.webp'), +(82, 'Harness Lead', 25.10, 9, 'Training and travel accessory. Harness Lead is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/082.webp'), +(83, 'Seatbelt Set', 32.21, 9, 'Training and travel accessory. Seatbelt Set is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/083.webp'), +(84, 'Travel Bag', 39.32, 9, 'Training and travel accessory. Travel Bag is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/084.webp'), +(85, 'Training Accessory', 46.43, 9, 'Training and travel accessory. Training Accessory is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/085.webp'), +(86, 'Recall Clip', 53.55, 9, 'Training and travel accessory. Recall Clip is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/086.webp'), +(87, 'Walking Tag', 60.66, 9, 'Training and travel accessory. Walking Tag is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/087.webp'), +(88, 'Crate Pack', 67.77, 9, 'Training and travel accessory. Crate Pack is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/088.webp'), +(89, 'Carrier Mat', 74.88, 9, 'Training and travel accessory. Carrier Mat is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/089.webp'), +(90, 'Adventure Guide', 81.99, 9, 'Training and travel accessory. Adventure Guide is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/090.webp'), +(91, 'Chicken Treats', 7.44, 10, 'Treat or chew for reward-based care. Chicken Treats is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/091.webp'), +(92, 'Duck Chews', 9.17, 10, 'Treat or chew for reward-based care. Duck Chews is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/092.webp'), +(93, 'Salmon Bites', 10.91, 10, 'Treat or chew for reward-based care. Salmon Bites is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/093.webp'), +(94, 'Pumpkin Snacks', 12.64, 10, 'Treat or chew for reward-based care. Pumpkin Snacks is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/094.webp'), +(95, 'Crunchy Rewards', 14.37, 10, 'Treat or chew for reward-based care. Crunchy Rewards is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/095.webp'), +(96, 'Soft-Bake Jerky', 16.11, 10, 'Treat or chew for reward-based care. Soft-Bake Jerky is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/096.webp'), +(97, 'Dental Cubes', 17.84, 10, 'Treat or chew for reward-based care. Dental Cubes is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/097.webp'), +(98, 'Mini Crisps', 19.57, 10, 'Treat or chew for reward-based care. Mini Crisps is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/098.webp'), +(99, 'Natural Morsels', 21.31, 10, 'Treat or chew for reward-based care. Natural Morsels is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/099.webp'), +(100, 'Freeze-Dried Biscuits', 23.04, 10, 'Treat or chew for reward-based care. Freeze-Dried Biscuits is stocked in all locations and priced for daily retail.', 'https://images.petshop.local/products/100.webp'); + +INSERT INTO productSupplier (supId, prodId, cost) VALUES +(1, 1, 14.55), +(1, 2, 18.61), +(6, 2, 19.91), +(1, 3, 23.00), +(1, 4, 27.71), +(6, 4, 29.10), +(1, 5, 32.75), +(1, 6, 38.11), +(6, 6, 39.25), +(1, 7, 31.69), +(1, 8, 36.57), +(6, 8, 39.13), +(1, 9, 41.77), +(1, 10, 47.29), +(6, 10, 49.65), +(2, 11, 5.02), +(2, 12, 6.80), +(7, 12, 7.00), +(2, 13, 8.71), +(2, 14, 7.79), +(7, 14, 8.34), +(2, 15, 9.50), +(2, 16, 11.35), +(7, 16, 11.92), +(2, 17, 13.32), +(2, 18, 15.44), +(7, 18, 15.90), +(2, 19, 17.69), +(2, 20, 20.07), +(7, 20, 21.47), +(9, 21, 5.99), +(9, 22, 8.85), +(3, 22, 9.29), +(9, 23, 11.96), +(9, 24, 15.33), +(3, 24, 15.79), +(9, 25, 18.97), +(9, 26, 22.87), +(3, 26, 24.47), +(9, 27, 27.02), +(9, 28, 22.76), +(3, 28, 23.90), +(9, 29, 26.52), +(9, 30, 30.55), +(3, 30, 31.47), +(8, 31, 13.63), +(8, 32, 22.78), +(3, 32, 24.37), +(8, 33, 32.70), +(8, 34, 43.38), +(3, 34, 45.55), +(8, 35, 39.67), +(8, 36, 49.21), +(3, 36, 50.69), +(8, 37, 59.51), +(8, 38, 70.57), +(3, 38, 75.51), +(8, 39, 82.40), +(8, 40, 94.98), +(3, 40, 99.73), +(3, 41, 8.06), +(3, 42, 9.27), +(10, 42, 9.55), +(3, 43, 13.15), +(3, 44, 17.39), +(10, 44, 18.61), +(3, 45, 21.97), +(3, 46, 26.90), +(10, 46, 28.25), +(3, 47, 32.18), +(3, 48, 37.81), +(10, 48, 38.94), +(3, 49, 31.69), +(3, 50, 36.80), +(10, 50, 39.38), +(12, 51, 8.59), +(12, 52, 11.92), +(4, 52, 12.52), +(12, 53, 15.52), +(12, 54, 19.38), +(4, 54, 19.96), +(12, 55, 23.52), +(12, 56, 20.22), +(4, 56, 21.64), +(12, 57, 23.95), +(12, 58, 27.95), +(4, 58, 29.35), +(12, 59, 32.23), +(12, 60, 36.78), +(4, 60, 37.88), +(4, 61, 7.48), +(4, 62, 9.56), +(5, 62, 10.23), +(4, 63, 8.52), +(4, 64, 10.38), +(5, 64, 10.90), +(4, 65, 12.38), +(4, 66, 14.52), +(5, 66, 14.96), +(4, 67, 16.81), +(4, 68, 19.24), +(5, 68, 20.59), +(4, 69, 21.82), +(4, 70, 17.76), +(5, 70, 18.65), +(10, 71, 22.03), +(10, 72, 32.93), +(3, 72, 33.92), +(10, 73, 44.79), +(10, 74, 57.61), +(3, 74, 61.64), +(10, 75, 71.39), +(10, 76, 86.13), +(3, 76, 90.44), +(10, 77, 73.69), +(10, 78, 86.99), +(3, 78, 89.60), +(10, 79, 101.25), +(10, 80, 116.47), +(3, 80, 124.62), +(11, 81, 12.05), +(11, 82, 17.57), +(5, 82, 18.45), +(11, 83, 23.51), +(11, 84, 21.63), +(5, 84, 22.28), +(11, 85, 26.93), +(11, 86, 32.67), +(5, 86, 34.96), +(11, 87, 38.82), +(11, 88, 45.41), +(5, 88, 47.68), +(11, 89, 52.42), +(11, 90, 59.85), +(5, 90, 61.65), +(1, 91, 4.09), +(1, 92, 5.32), +(5, 92, 5.69), +(1, 93, 6.66), +(1, 94, 8.09), +(5, 94, 8.49), +(1, 95, 9.63), +(1, 96, 11.28), +(5, 96, 11.62), +(1, 97, 13.02), +(1, 98, 10.76), +(5, 98, 11.51), +(1, 99, 12.36), +(1, 100, 14.05), +(5, 100, 14.75); + +INSERT INTO inventory (inventoryId, storeId, prodId, quantity) VALUES +(1, 1, 1, 52), +(2, 1, 2, 59), +(3, 1, 3, 66), +(4, 1, 4, 73), +(5, 1, 5, 80), +(6, 1, 6, 87), +(7, 1, 7, 29), +(8, 1, 8, 36), +(9, 1, 9, 43), +(10, 1, 10, 50), +(11, 1, 11, 37), +(12, 1, 12, 44), +(13, 1, 13, 51), +(14, 1, 14, 58), +(15, 1, 15, 65), +(16, 1, 16, 72), +(17, 1, 17, 14), +(18, 1, 18, 21), +(19, 1, 19, 28), +(20, 1, 20, 35), +(21, 1, 21, 42), +(22, 1, 22, 49), +(23, 1, 23, 56), +(24, 1, 24, 63), +(25, 1, 25, 70), +(26, 1, 26, 12), +(27, 1, 27, 19), +(28, 1, 28, 26), +(29, 1, 29, 33), +(30, 1, 30, 40), +(31, 1, 31, 8), +(32, 1, 32, 13), +(33, 1, 33, 18), +(34, 1, 34, 5), +(35, 1, 35, 10), +(36, 1, 36, 15), +(37, 1, 37, 20), +(38, 1, 38, 7), +(39, 1, 39, 12), +(40, 1, 40, 17), +(41, 1, 41, 52), +(42, 1, 42, 59), +(43, 1, 43, 66), +(44, 1, 44, 8), +(45, 1, 45, 15), +(46, 1, 46, 22), +(47, 1, 47, 29), +(48, 1, 48, 36), +(49, 1, 49, 43), +(50, 1, 50, 50), +(51, 1, 51, 57), +(52, 1, 52, 64), +(53, 1, 53, 71), +(54, 1, 54, 13), +(55, 1, 55, 20), +(56, 1, 56, 27), +(57, 1, 57, 34), +(58, 1, 58, 41), +(59, 1, 59, 48), +(60, 1, 60, 55), +(61, 1, 61, 62), +(62, 1, 62, 69), +(63, 1, 63, 11), +(64, 1, 64, 18), +(65, 1, 65, 25), +(66, 1, 66, 32), +(67, 1, 67, 39), +(68, 1, 68, 46), +(69, 1, 69, 53), +(70, 1, 70, 60), +(71, 1, 71, 10), +(72, 1, 72, 15), +(73, 1, 73, 20), +(74, 1, 74, 7), +(75, 1, 75, 12), +(76, 1, 76, 17), +(77, 1, 77, 4), +(78, 1, 78, 9), +(79, 1, 79, 14), +(80, 1, 80, 19), +(81, 1, 81, 72), +(82, 1, 82, 14), +(83, 1, 83, 21), +(84, 1, 84, 28), +(85, 1, 85, 35), +(86, 1, 86, 42), +(87, 1, 87, 49), +(88, 1, 88, 56), +(89, 1, 89, 63), +(90, 1, 90, 70), +(91, 1, 91, 32), +(92, 1, 92, 39), +(93, 1, 93, 46), +(94, 1, 94, 53), +(95, 1, 95, 60), +(96, 1, 96, 67), +(97, 1, 97, 74), +(98, 1, 98, 81), +(99, 1, 99, 88), +(100, 1, 100, 30), +(101, 2, 1, 69), +(102, 2, 2, 76), +(103, 2, 3, 83), +(104, 2, 4, 90), +(105, 2, 5, 32), +(106, 2, 6, 39), +(107, 2, 7, 46), +(108, 2, 8, 53), +(109, 2, 9, 60), +(110, 2, 10, 67), +(111, 2, 11, 54), +(112, 2, 12, 61), +(113, 2, 13, 68), +(114, 2, 14, 10), +(115, 2, 15, 17), +(116, 2, 16, 24), +(117, 2, 17, 31), +(118, 2, 18, 38), +(119, 2, 19, 45), +(120, 2, 20, 52), +(121, 2, 21, 59), +(122, 2, 22, 66), +(123, 2, 23, 8), +(124, 2, 24, 15), +(125, 2, 25, 22), +(126, 2, 26, 29), +(127, 2, 27, 36), +(128, 2, 28, 43), +(129, 2, 29, 50), +(130, 2, 30, 57), +(131, 2, 31, 19), +(132, 2, 32, 6), +(133, 2, 33, 11), +(134, 2, 34, 16), +(135, 2, 35, 21), +(136, 2, 36, 8), +(137, 2, 37, 13), +(138, 2, 38, 18), +(139, 2, 39, 5), +(140, 2, 40, 10), +(141, 2, 41, 69), +(142, 2, 42, 11), +(143, 2, 43, 18), +(144, 2, 44, 25), +(145, 2, 45, 32), +(146, 2, 46, 39), +(147, 2, 47, 46), +(148, 2, 48, 53), +(149, 2, 49, 60), +(150, 2, 50, 67), +(151, 2, 51, 9), +(152, 2, 52, 16), +(153, 2, 53, 23), +(154, 2, 54, 30), +(155, 2, 55, 37), +(156, 2, 56, 44), +(157, 2, 57, 51), +(158, 2, 58, 58), +(159, 2, 59, 65), +(160, 2, 60, 72), +(161, 2, 61, 14), +(162, 2, 62, 21), +(163, 2, 63, 28), +(164, 2, 64, 35), +(165, 2, 65, 42), +(166, 2, 66, 49), +(167, 2, 67, 56), +(168, 2, 68, 63), +(169, 2, 69, 70), +(170, 2, 70, 12), +(171, 2, 71, 21), +(172, 2, 72, 8), +(173, 2, 73, 13), +(174, 2, 74, 18), +(175, 2, 75, 5), +(176, 2, 76, 10), +(177, 2, 77, 15), +(178, 2, 78, 20), +(179, 2, 79, 7), +(180, 2, 80, 12), +(181, 2, 81, 24), +(182, 2, 82, 31), +(183, 2, 83, 38), +(184, 2, 84, 45), +(185, 2, 85, 52), +(186, 2, 86, 59), +(187, 2, 87, 66), +(188, 2, 88, 8), +(189, 2, 89, 15), +(190, 2, 90, 22), +(191, 2, 91, 49), +(192, 2, 92, 56), +(193, 2, 93, 63), +(194, 2, 94, 70), +(195, 2, 95, 77), +(196, 2, 96, 84), +(197, 2, 97, 91), +(198, 2, 98, 33), +(199, 2, 99, 40), +(200, 2, 100, 47), +(201, 3, 1, 86), +(202, 3, 2, 28), +(203, 3, 3, 35), +(204, 3, 4, 42), +(205, 3, 5, 49), +(206, 3, 6, 56), +(207, 3, 7, 63), +(208, 3, 8, 70), +(209, 3, 9, 77), +(210, 3, 10, 84), +(211, 3, 11, 71), +(212, 3, 12, 13), +(213, 3, 13, 20), +(214, 3, 14, 27), +(215, 3, 15, 34), +(216, 3, 16, 41), +(217, 3, 17, 48), +(218, 3, 18, 55), +(219, 3, 19, 62), +(220, 3, 20, 69), +(221, 3, 21, 11), +(222, 3, 22, 18), +(223, 3, 23, 25), +(224, 3, 24, 32), +(225, 3, 25, 39), +(226, 3, 26, 46), +(227, 3, 27, 53), +(228, 3, 28, 60), +(229, 3, 29, 67), +(230, 3, 30, 9), +(231, 3, 31, 12), +(232, 3, 32, 17), +(233, 3, 33, 4), +(234, 3, 34, 9), +(235, 3, 35, 14), +(236, 3, 36, 19), +(237, 3, 37, 6), +(238, 3, 38, 11), +(239, 3, 39, 16), +(240, 3, 40, 21), +(241, 3, 41, 21), +(242, 3, 42, 28), +(243, 3, 43, 35), +(244, 3, 44, 42), +(245, 3, 45, 49), +(246, 3, 46, 56), +(247, 3, 47, 63), +(248, 3, 48, 70), +(249, 3, 49, 12), +(250, 3, 50, 19), +(251, 3, 51, 26), +(252, 3, 52, 33), +(253, 3, 53, 40), +(254, 3, 54, 47), +(255, 3, 55, 54), +(256, 3, 56, 61), +(257, 3, 57, 68), +(258, 3, 58, 10), +(259, 3, 59, 17), +(260, 3, 60, 24), +(261, 3, 61, 31), +(262, 3, 62, 38), +(263, 3, 63, 45), +(264, 3, 64, 52), +(265, 3, 65, 59), +(266, 3, 66, 66), +(267, 3, 67, 8), +(268, 3, 68, 15), +(269, 3, 69, 22), +(270, 3, 70, 29), +(271, 3, 71, 14), +(272, 3, 72, 19), +(273, 3, 73, 6), +(274, 3, 74, 11), +(275, 3, 75, 16), +(276, 3, 76, 21), +(277, 3, 77, 8), +(278, 3, 78, 13), +(279, 3, 79, 18), +(280, 3, 80, 5), +(281, 3, 81, 41), +(282, 3, 82, 48), +(283, 3, 83, 55), +(284, 3, 84, 62), +(285, 3, 85, 69), +(286, 3, 86, 11), +(287, 3, 87, 18), +(288, 3, 88, 25), +(289, 3, 89, 32), +(290, 3, 90, 39), +(291, 3, 91, 66), +(292, 3, 92, 73), +(293, 3, 93, 80), +(294, 3, 94, 87), +(295, 3, 95, 29), +(296, 3, 96, 36), +(297, 3, 97, 43), +(298, 3, 98, 50), +(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 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); + +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), +(2, 'Milo', 'Dog', 'Beagle', 3, 'Available', 513.60, 'https://images.petshop.local/pets/002.webp', NULL, 1), +(3, 'Charlie', 'Dog', 'Husky', 4, 'Available', 560.40, 'https://images.petshop.local/pets/003.webp', NULL, 1), +(4, 'Luna', 'Cat', 'Persian', 5, 'Available', 395.20, 'https://images.petshop.local/pets/004.webp', NULL, 1), +(5, 'Max', 'Cat', 'Siamese', 6, 'Available', 429.00, 'https://images.petshop.local/pets/005.webp', NULL, 1), +(6, 'Bella', 'Rabbit', 'Dutch', 1, 'Available', 211.40, 'https://images.petshop.local/pets/006.webp', NULL, 1), +(7, 'Rocky', 'Bird', 'Budgie', 2, 'Available', 169.20, 'https://images.petshop.local/pets/007.webp', NULL, 1), +(8, 'Daisy', 'Fish', 'Guppy', 3, 'Available', 16.20, 'https://images.petshop.local/pets/008.webp', NULL, 1), +(9, 'Cooper', 'Hamster', 'Roborovski', 4, 'Available', 30.10, 'https://images.petshop.local/pets/009.webp', NULL, 1), +(10, 'Ruby', 'Guinea Pig', 'Abyssinian', 5, 'Available', 53.50, 'https://images.petshop.local/pets/010.webp', NULL, 1), +(11, 'Tucker', 'Dog', 'Border Collie', 6, 'Available', 574.80, 'https://images.petshop.local/pets/011.webp', NULL, 1), +(12, 'Rosie', 'Cat', 'Maine Coon', 1, 'Available', 405.60, 'https://images.petshop.local/pets/012.webp', NULL, 1), +(13, 'Bear', 'Dog', 'Labrador', 2, 'Available', 668.40, 'https://images.petshop.local/pets/013.webp', NULL, 2), +(14, 'Maggie', 'Dog', 'Golden Retriever', 3, 'Available', 715.20, 'https://images.petshop.local/pets/014.webp', NULL, 2), +(15, 'Leo', 'Dog', 'Husky', 4, 'Available', 762.00, 'https://images.petshop.local/pets/015.webp', NULL, 2), +(16, 'Zoey', 'Cat', 'Scottish Fold', 5, 'Available', 280.80, 'https://images.petshop.local/pets/016.webp', NULL, 2), +(17, 'Oliver', 'Cat', 'Siamese', 6, 'Available', 314.60, 'https://images.petshop.local/pets/017.webp', NULL, 2), +(18, 'Lola', 'Rabbit', 'Netherland Dwarf', 1, 'Available', 154.20, 'https://images.petshop.local/pets/018.webp', NULL, 2), +(19, 'Buster', 'Bird', 'Budgie', 2, 'Available', 116.40, 'https://images.petshop.local/pets/019.webp', NULL, 2), +(20, 'Sadie', 'Fish', 'Tetra', 3, 'Available', 33.00, 'https://images.petshop.local/pets/020.webp', NULL, 2), +(21, 'Toby', 'Hamster', 'Dwarf', 4, 'Available', 46.90, 'https://images.petshop.local/pets/021.webp', NULL, 2), +(22, 'Cleo', 'Guinea Pig', 'Abyssinian', 5, 'Available', 78.70, 'https://images.petshop.local/pets/022.webp', NULL, 2), +(23, 'Harley', 'Dog', 'Boxer', 6, 'Available', 776.40, 'https://images.petshop.local/pets/023.webp', NULL, 2), +(24, 'Mocha', 'Cat', 'Siamese', 1, 'Available', 291.20, 'https://images.petshop.local/pets/024.webp', NULL, 2), +(25, 'Rex', 'Dog', 'Poodle', 2, 'Available', 510.00, 'https://images.petshop.local/pets/025.webp', NULL, 3), +(26, 'Willow', 'Dog', 'Boxer', 3, 'Available', 556.80, 'https://images.petshop.local/pets/026.webp', NULL, 3), +(27, 'Gizmo', 'Dog', 'Labrador', 4, 'Available', 603.60, 'https://images.petshop.local/pets/027.webp', NULL, 3), +(28, 'Nala', 'Cat', 'Calico', 5, 'Available', 426.40, 'https://images.petshop.local/pets/028.webp', NULL, 3), +(29, 'Duke', 'Cat', 'Calico', 6, 'Available', 460.20, 'https://images.petshop.local/pets/029.webp', NULL, 3), +(30, 'Misty', 'Rabbit', 'Lionhead', 1, 'Available', 227.00, 'https://images.petshop.local/pets/030.webp', NULL, 3), +(31, 'Ace', 'Bird', 'Budgie', 2, 'Available', 63.60, 'https://images.petshop.local/pets/031.webp', NULL, 3), +(32, 'Pepper', 'Fish', 'Goldfish', 3, 'Available', 19.80, 'https://images.petshop.local/pets/032.webp', NULL, 3), +(33, 'Coco', 'Hamster', 'Syrian', 4, 'Available', 33.70, 'https://images.petshop.local/pets/033.webp', NULL, 3), +(34, 'Finn', 'Guinea Pig', 'Peruvian', 5, 'Available', 58.90, 'https://images.petshop.local/pets/034.webp', NULL, 3), +(35, 'Shadow', 'Dog', 'Beagle', 6, 'Available', 618.00, 'https://images.petshop.local/pets/035.webp', NULL, 3), +(36, 'Kitty', 'Cat', 'British Shorthair', 1, 'Available', 436.80, 'https://images.petshop.local/pets/036.webp', NULL, 3), +(37, 'Bruno', 'Bird', 'Cockatiel', 6, 'Adopted', 94.80, 'https://images.petshop.local/pets/037.webp', 16, 1), +(38, 'Snowball', 'Fish', 'Betta', 8, 'Adopted', 28.80, 'https://images.petshop.local/pets/038.webp', 17, 2), +(39, 'Zeus', 'Fish', 'Guppy', 2, 'Adopted', 33.90, 'https://images.petshop.local/pets/039.webp', 18, 3), +(40, 'Biscuit', 'Fish', 'Goldfish', 4, 'Adopted', 39.00, 'https://images.petshop.local/pets/040.webp', 19, 1), +(41, 'Patches', 'Dog', 'Boxer', 6, 'Adopted', 769.20, 'https://images.petshop.local/pets/041.webp', 20, 2), +(42, 'Scout', 'Fish', 'Goldfish', 8, 'Adopted', 19.20, 'https://images.petshop.local/pets/042.webp', 21, 3), +(43, 'Mittens', 'Rabbit', 'Holland Lop', 2, 'Adopted', 150.30, 'https://images.petshop.local/pets/043.webp', 22, 1), +(44, 'Thor', 'Fish', 'Betta', 4, 'Adopted', 29.40, 'https://images.petshop.local/pets/044.webp', 23, 2), +(45, 'Whiskers', 'Fish', 'Betta', 6, 'Adopted', 34.50, 'https://images.petshop.local/pets/045.webp', 24, 3), +(46, 'Goldie', 'Fish', 'Goldfish', 8, 'Adopted', 39.60, 'https://images.petshop.local/pets/046.webp', 25, 1), +(47, 'Midnight', 'Bird', 'Parakeet', 2, 'Adopted', 178.80, 'https://images.petshop.local/pets/047.webp', 26, 2), +(48, 'Storm', 'Bird', 'Canary', 4, 'Adopted', 79.20, 'https://images.petshop.local/pets/048.webp', 27, 3), +(49, 'Peanut', 'Bird', 'Parakeet', 6, 'Adopted', 99.60, 'https://images.petshop.local/pets/049.webp', 28, 1), +(50, 'Daisy', 'Bird', 'Canary', 8, 'Adopted', 120.00, 'https://images.petshop.local/pets/050.webp', 29, 2), +(51, 'Cleo', 'Rabbit', 'Netherland Dwarf', 2, 'Adopted', 197.10, 'https://images.petshop.local/pets/051.webp', 30, 3), +(52, 'Sunny', 'Cat', 'Maine Coon', 4, 'Adopted', 478.40, 'https://images.petshop.local/pets/052.webp', 31, 1), +(53, 'Maple', 'Dog', 'Boxer', 6, 'Adopted', 423.60, 'https://images.petshop.local/pets/053.webp', 32, 2), +(54, 'Nova', 'Rabbit', 'Dutch', 8, 'Adopted', 133.40, 'https://images.petshop.local/pets/054.webp', 33, 3), +(55, 'Piper', 'Dog', 'Shih Tzu', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/055.webp', 34, 2), +(56, 'Hazel', 'Cat', 'Bengal', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/056.webp', 35, 3), +(57, 'Jasper', 'Rabbit', 'Lionhead', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/057.webp', 36, 1), +(58, 'Remy', 'Bird', 'Canary', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/058.webp', 37, 2), +(59, 'Archie', 'Fish', 'Tetra', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/059.webp', 38, 3), +(60, 'Skye', 'Hamster', 'Syrian', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/060.webp', 39, 1), +(61, 'Otis', 'Guinea Pig', 'Abyssinian', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/061.webp', 40, 2), +(62, 'Marley', 'Dog', 'Border Collie', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/062.webp', 41, 3), +(63, 'Blue', 'Cat', 'Scottish Fold', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/063.webp', 42, 1), +(64, 'Honey', 'Rabbit', 'Netherland Dwarf', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/064.webp', 43, 2), +(65, 'Mochi', 'Bird', 'Canary', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/065.webp', 44, 3), +(66, 'Kiki', 'Fish', 'Goldfish', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/066.webp', 45, 1), +(67, 'River', 'Hamster', 'Dwarf', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/067.webp', 46, 2), +(68, 'Bowie', 'Guinea Pig', 'American', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/068.webp', 47, 3), +(69, 'Sage', 'Dog', 'Labrador', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/069.webp', 48, 1), +(70, 'Echo', 'Cat', 'Siamese', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/070.webp', 49, 2), +(71, 'Poppy', 'Rabbit', 'Dutch', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/071.webp', 50, 3), +(72, 'Juniper', 'Bird', 'Parakeet', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/072.webp', 51, 1), +(73, 'Winston', 'Fish', 'Guppy', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/073.webp', 52, 2), +(74, 'Freya', 'Hamster', 'Dwarf', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/074.webp', 53, 3), +(75, 'Finnley', 'Guinea Pig', 'Peruvian', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/075.webp', 54, 1), +(76, 'Louie', 'Dog', 'Corgi', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/076.webp', 55, 2), +(77, 'Ivy', 'Cat', 'Calico', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/077.webp', 56, 3), +(78, 'Binx', 'Rabbit', 'Lionhead', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/078.webp', 57, 1), +(79, 'Suki', 'Bird', 'Parakeet', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/079.webp', 58, 2), +(80, 'Mabel', 'Fish', 'Molly', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/080.webp', 59, 3), +(81, 'Rolo', 'Hamster', 'Syrian', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/081.webp', 60, 1), +(82, 'Clover', 'Guinea Pig', 'Abyssinian', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/082.webp', 61, 2), +(83, 'Frankie', 'Dog', 'German Shepherd', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/083.webp', 62, 3), +(84, 'Tilly', 'Cat', 'Tabby', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/084.webp', 63, 1), +(85, 'Rory', 'Rabbit', 'Holland Lop', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/085.webp', 64, 2), +(86, 'Gus', 'Bird', 'Budgie', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/086.webp', 65, 3), +(87, 'Peaches', 'Fish', 'Guppy', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/087.webp', 66, 1), +(88, 'Indie', 'Hamster', 'Roborovski', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/088.webp', 67, 2), +(89, 'Minnie', 'Guinea Pig', 'Peruvian', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/089.webp', 68, 3), +(90, 'Koda', 'Dog', 'Shih Tzu', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/090.webp', 69, 1), +(91, 'Mango', 'Cat', 'British Shorthair', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/091.webp', 70, 2), +(92, 'Pearl', 'Rabbit', 'Lionhead', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/092.webp', 71, 3), +(93, 'Onyx', 'Bird', 'Canary', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/093.webp', 72, 1), +(94, 'Pumpkin', 'Fish', 'Betta', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/094.webp', 73, 2), +(95, 'Nori', 'Hamster', 'Dwarf', 4, 'Owned', 0.00, 'https://images.petshop.local/pets/095.webp', 74, 3), +(96, 'Cosmo', 'Guinea Pig', 'American', 6, 'Owned', 0.00, 'https://images.petshop.local/pets/096.webp', 75, 1), +(97, 'Ziggy', 'Dog', 'Beagle', 8, 'Owned', 0.00, 'https://images.petshop.local/pets/097.webp', 76, 2), +(98, 'Bean', 'Cat', 'Calico', 10, 'Owned', 0.00, 'https://images.petshop.local/pets/098.webp', 77, 3), +(99, 'Flora', 'Rabbit', 'Holland Lop', 12, 'Owned', 0.00, 'https://images.petshop.local/pets/099.webp', 78, 1), +(100, 'Comet', 'Bird', 'Lovebird', 2, 'Owned', 0.00, 'https://images.petshop.local/pets/100.webp', 79, 2); + +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'); + +INSERT INTO adoption (adoptionId, petId, customerId, employeeId, sourceStoreId, adoptionDate, adoptionStatus) VALUES +(1, 37, 16, 3, 1, '2026-01-08', 'Completed'), +(2, 38, 17, 8, 2, '2026-01-13', 'Completed'), +(3, 39, 18, 13, 3, '2026-01-18', 'Completed'), +(4, 40, 19, 6, 1, '2026-01-23', 'Completed'), +(5, 41, 20, 7, 2, '2026-01-28', 'Completed'), +(6, 42, 21, 12, 3, '2026-02-02', 'Completed'), +(7, 43, 22, 5, 1, '2026-02-07', 'Completed'), +(8, 44, 23, 10, 2, '2026-02-12', 'Completed'), +(9, 45, 24, 11, 3, '2026-02-17', 'Completed'), +(10, 46, 25, 4, 1, '2026-02-22', 'Completed'), +(11, 47, 26, 9, 2, '2026-02-27', 'Completed'), +(12, 48, 27, 14, 3, '2026-03-04', 'Completed'), +(13, 49, 28, 3, 1, '2026-03-09', 'Completed'), +(14, 50, 29, 8, 2, '2026-03-14', 'Completed'), +(15, 51, 30, 13, 3, '2026-03-19', 'Completed'), +(16, 52, 31, 6, 1, '2026-03-24', 'Completed'), +(17, 53, 32, 7, 2, '2026-03-29', 'Completed'), +(18, 54, 33, 12, 3, '2026-04-03', 'Completed'); + +INSERT INTO cart (cartId, userId, storeId, couponId, cartStatus, subtotalAmount, discountAmount, totalAmount) VALUES +(1, 3, 1, 1, 'CHECKED_OUT', 44.51, 0.00, 44.51), +(2, 4, 1, 2, 'CHECKED_OUT', 252.00, 25.20, 226.80), +(3, 5, 1, 3, 'CHECKED_OUT', 432.33, 5.00, 427.33), +(4, 6, 1, 7, 'CHECKED_OUT', 108.67, 13.04, 95.63), +(5, 7, 2, 1, 'CHECKED_OUT', 512.62, 0.00, 512.62), +(6, 8, 2, 1, 'CHECKED_OUT', 481.83, 0.00, 481.83), +(7, 9, 2, 4, 'CHECKED_OUT', 78.06, 11.71, 66.35), +(8, 10, 2, 5, 'CHECKED_OUT', 212.94, 8.00, 204.94), +(9, 11, 3, 1, 'CHECKED_OUT', 729.31, 0.00, 729.31), +(10, 12, 3, 6, 'CHECKED_OUT', 221.87, 22.19, 199.68), +(11, 13, 3, 1, 'CHECKED_OUT', 162.19, 0.00, 162.19), +(12, 14, 3, 2, 'CHECKED_OUT', 593.40, 59.34, 534.06), +(13, 15, 1, 3, 'ACTIVE', 78.65, 5.00, 73.65), +(14, 16, 2, 7, 'ACTIVE', 140.29, 16.83, 123.46), +(15, 17, 3, 1, 'ACTIVE', 261.60, 0.00, 261.60), +(16, 18, 1, 1, 'ACTIVE', 93.84, 0.00, 93.84), +(17, 19, 2, 4, 'ACTIVE', 167.87, 25.18, 142.69), +(18, 20, 3, 5, 'ACTIVE', 206.96, 8.00, 198.96), +(19, 21, 1, 1, 'ACTIVE', 257.56, 0.00, 257.56), +(20, 22, 2, 6, 'ACTIVE', 258.59, 25.86, 232.73), +(21, 23, 3, 1, 'ACTIVE', 340.69, 0.00, 340.69), +(22, 24, 1, 2, 'ACTIVE', 152.52, 15.25, 137.27), +(23, 25, 2, 3, 'ACTIVE', 266.50, 5.00, 261.50), +(24, 26, 3, 7, 'ACTIVE', 231.01, 27.72, 203.29), +(25, 27, 1, 1, 'ACTIVE', 393.87, 0.00, 393.87), +(26, 28, 2, 1, 'ACTIVE', 437.53, 0.00, 437.53), +(27, 29, 3, 4, 'ACTIVE', 258.63, 38.79, 219.84), +(28, 30, 1, 5, 'ACTIVE', 132.17, 8.00, 124.17), +(29, 31, 2, 1, 'ACTIVE', 250.10, 0.00, 250.10), +(30, 32, 3, 6, 'ACTIVE', 498.48, 49.85, 448.63), +(31, 33, 1, 1, 'ABANDONED', 37.99, 0.00, 37.99), +(32, 34, 2, 2, 'ABANDONED', 318.98, 31.90, 287.08), +(33, 35, 3, 3, 'ABANDONED', 399.18, 5.00, 394.18), +(34, 36, 1, 7, 'ABANDONED', 79.36, 9.52, 69.84), +(35, 37, 2, 1, 'ABANDONED', 446.97, 0.00, 446.97), +(36, 38, 3, 1, 'ABANDONED', 714.69, 0.00, 714.69), +(37, 39, 1, 4, 'ABANDONED', 58.94, 0.00, 58.94), +(38, 40, 2, 5, 'ABANDONED', 248.85, 8.00, 240.85), +(39, 41, 3, 1, 'ABANDONED', 444.14, 0.00, 444.14), +(40, 42, 1, 6, 'ABANDONED', 178.85, 17.89, 160.96); + +INSERT INTO cart_item (cartItemId, cartId, prodId, quantity, unitPrice) VALUES +(1, 1, 1, 1, 25.09), +(2, 1, 12, 2, 9.71), +(3, 2, 8, 2, 63.05), +(4, 2, 19, 3, 25.27), +(5, 2, 30, 1, 50.09), +(6, 3, 15, 3, 16.38), +(7, 3, 26, 1, 32.67), +(8, 3, 37, 2, 97.56), +(9, 3, 48, 3, 51.80), +(10, 4, 22, 1, 15.25), +(11, 4, 33, 2, 46.71), +(12, 5, 29, 2, 45.73), +(13, 5, 40, 3, 135.69), +(14, 5, 51, 1, 14.09), +(15, 6, 36, 3, 84.85), +(16, 6, 47, 1, 45.97), +(17, 6, 58, 2, 45.82), +(18, 6, 69, 3, 29.89), +(19, 7, 43, 1, 22.68), +(20, 7, 54, 2, 27.69), +(21, 8, 50, 2, 63.44), +(22, 8, 61, 3, 10.69), +(23, 8, 72, 1, 53.99), +(24, 9, 57, 3, 41.29), +(25, 9, 68, 1, 27.49), +(26, 9, 79, 2, 165.99), +(27, 9, 90, 3, 81.99), +(28, 10, 64, 1, 17.89), +(29, 10, 75, 2, 101.99), +(30, 11, 71, 2, 37.99), +(31, 11, 82, 3, 25.10), +(32, 11, 93, 1, 10.91), +(33, 12, 78, 3, 149.99), +(34, 12, 89, 1, 74.88), +(35, 12, 100, 2, 23.04), +(36, 12, 11, 3, 7.49), +(37, 13, 85, 1, 46.43), +(38, 13, 96, 2, 16.11), +(39, 14, 92, 2, 9.17), +(40, 14, 3, 3, 35.93), +(41, 14, 14, 1, 14.16), +(42, 15, 99, 3, 21.31), +(43, 15, 10, 1, 73.89), +(44, 15, 21, 2, 10.89), +(45, 15, 32, 3, 34.00), +(46, 16, 6, 1, 52.20), +(47, 16, 17, 2, 20.82), +(48, 17, 13, 2, 11.93), +(49, 17, 24, 3, 23.96), +(50, 17, 35, 1, 72.13), +(51, 18, 20, 3, 27.49), +(52, 18, 31, 1, 21.29), +(53, 18, 42, 2, 16.86), +(54, 18, 53, 3, 23.16), +(55, 19, 27, 1, 37.02), +(56, 19, 38, 2, 110.27), +(57, 20, 34, 2, 59.42), +(58, 20, 45, 3, 34.33), +(59, 20, 56, 1, 36.76), +(60, 21, 41, 3, 11.04), +(61, 21, 52, 1, 18.62), +(62, 21, 63, 2, 15.49), +(63, 21, 74, 3, 85.99), +(64, 22, 48, 1, 51.80), +(65, 22, 59, 2, 50.36), +(66, 23, 55, 2, 32.22), +(67, 23, 66, 3, 22.69), +(68, 23, 77, 1, 133.99), +(69, 24, 62, 3, 13.09), +(70, 24, 73, 1, 69.99), +(71, 24, 84, 2, 39.32), +(72, 24, 95, 3, 14.37), +(73, 25, 69, 1, 29.89), +(74, 25, 80, 2, 181.99), +(75, 26, 76, 2, 117.99), +(76, 26, 87, 3, 60.66), +(77, 26, 98, 1, 19.57), +(78, 27, 83, 3, 32.21), +(79, 27, 94, 1, 12.64), +(80, 27, 5, 2, 46.78), +(81, 27, 16, 3, 18.60), +(82, 28, 90, 1, 81.99), +(83, 28, 1, 2, 25.09), +(84, 29, 97, 2, 17.84), +(85, 29, 8, 3, 63.05), +(86, 29, 19, 1, 25.27), +(87, 30, 4, 3, 41.36), +(88, 30, 15, 1, 16.38), +(89, 30, 26, 2, 32.67), +(90, 30, 37, 3, 97.56), +(91, 31, 11, 1, 7.49), +(92, 31, 22, 2, 15.25), +(93, 32, 18, 2, 23.05), +(94, 32, 29, 3, 45.73), +(95, 32, 40, 1, 135.69), +(96, 33, 25, 3, 28.31), +(97, 33, 36, 1, 84.85), +(98, 33, 47, 2, 45.97), +(99, 33, 58, 3, 45.82), +(100, 34, 32, 1, 34.00), +(101, 34, 43, 2, 22.68), +(102, 35, 39, 2, 122.98), +(103, 35, 50, 3, 63.44), +(104, 35, 61, 1, 10.69), +(105, 36, 46, 3, 40.15), +(106, 36, 57, 1, 41.29), +(107, 36, 68, 2, 27.49), +(108, 36, 79, 3, 165.99), +(109, 37, 53, 1, 23.16), +(110, 37, 64, 2, 17.89), +(111, 38, 60, 2, 54.89), +(112, 38, 71, 3, 37.99), +(113, 38, 82, 1, 25.10), +(114, 39, 67, 3, 25.09), +(115, 39, 78, 1, 149.99), +(116, 39, 89, 2, 74.88), +(117, 39, 100, 3, 23.04), +(118, 40, 74, 1, 85.99), +(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); + +INSERT INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES +(1, 1, 1, 1, 25.09), +(2, 1, 12, 2, 9.71), +(3, 2, 8, 2, 63.05), +(4, 2, 19, 3, 25.27), +(5, 2, 30, 1, 50.09), +(6, 3, 15, 3, 16.38), +(7, 3, 26, 1, 32.67), +(8, 3, 37, 2, 97.56), +(9, 3, 48, 3, 51.80), +(10, 4, 22, 1, 15.25), +(11, 4, 33, 2, 46.71), +(12, 5, 29, 2, 45.73), +(13, 5, 40, 3, 135.69), +(14, 5, 51, 1, 14.09), +(15, 6, 36, 3, 84.85), +(16, 6, 47, 1, 45.97), +(17, 6, 58, 2, 45.82), +(18, 6, 69, 3, 29.89), +(19, 7, 43, 1, 22.68), +(20, 7, 54, 2, 27.69), +(21, 8, 50, 2, 63.44), +(22, 8, 61, 3, 10.69), +(23, 8, 72, 1, 53.99), +(24, 9, 57, 3, 41.29), +(25, 9, 68, 1, 27.49), +(26, 9, 79, 2, 165.99), +(27, 9, 90, 3, 81.99), +(28, 10, 64, 1, 17.89), +(29, 10, 75, 2, 101.99), +(30, 11, 71, 2, 37.99), +(31, 11, 82, 3, 25.10), +(32, 11, 93, 1, 10.91), +(33, 12, 78, 3, 149.99), +(34, 12, 89, 1, 74.88), +(35, 12, 100, 2, 23.04), +(36, 12, 11, 3, 7.49), +(37, 13, 4, 2, 41.36), +(38, 14, 9, 1, 68.47), +(39, 14, 26, 2, 32.67), +(40, 15, 14, 2, 14.16), +(41, 15, 31, 1, 21.29), +(42, 15, 48, 2, 51.80), +(43, 16, 19, 1, 25.27), +(44, 17, 24, 2, 23.96), +(45, 17, 41, 1, 11.04), +(46, 18, 29, 1, 45.73), +(47, 18, 46, 2, 40.15), +(48, 18, 63, 1, 15.49), +(49, 19, 34, 2, 59.42), +(50, 20, 39, 1, 122.98), +(51, 20, 56, 2, 36.76), +(52, 21, 44, 2, 28.51), +(53, 21, 61, 1, 10.69), +(54, 21, 78, 2, 149.99), +(55, 22, 49, 1, 57.62), +(56, 23, 54, 2, 27.69), +(57, 23, 71, 1, 37.99), +(58, 24, 59, 1, 50.36), +(59, 24, 76, 2, 117.99), +(60, 24, 93, 1, 10.91), +(61, 25, 64, 2, 17.89), +(62, 26, 69, 1, 29.89), +(63, 26, 86, 2, 53.55), +(64, 27, 74, 2, 85.99), +(65, 27, 91, 1, 7.44), +(66, 27, 8, 2, 63.05), +(67, 28, 79, 1, 165.99), +(68, 29, 84, 2, 39.32), +(69, 29, 1, 1, 25.09), +(70, 30, 89, 1, 74.88), +(71, 30, 6, 2, 52.20), +(72, 30, 23, 1, 19.60), +(73, 31, 94, 2, 12.64), +(74, 32, 99, 1, 21.31), +(75, 32, 16, 2, 18.60), +(76, 33, 4, 2, 41.36), +(77, 33, 21, 1, 10.89), +(78, 33, 38, 2, 110.27), +(79, 34, 9, 1, 68.47), +(80, 35, 14, 2, 14.16), +(81, 35, 31, 1, 21.29), +(82, 36, 19, 1, 25.27), +(83, 36, 36, 2, 84.85), +(84, 36, 53, 1, 23.16), +(85, 37, 24, 2, 23.96), +(86, 38, 29, 1, 45.73), +(87, 38, 46, 2, 40.15), +(88, 39, 34, 2, 59.42), +(89, 39, 51, 1, 14.09), +(90, 39, 68, 2, 27.49), +(91, 40, 39, 1, 122.98), +(92, 41, 44, 2, 28.51), +(93, 41, 61, 1, 10.69), +(94, 42, 49, 1, 57.62), +(95, 42, 66, 2, 22.69), +(96, 42, 83, 1, 32.21), +(97, 43, 54, 2, 27.69), +(98, 44, 59, 1, 50.36), +(99, 44, 76, 2, 117.99), +(100, 45, 64, 2, 17.89), +(101, 45, 81, 1, 17.99), +(102, 45, 98, 2, 19.57), +(103, 46, 69, 1, 29.89), +(104, 47, 74, 2, 85.99), +(105, 47, 91, 1, 7.44), +(106, 48, 79, 1, 165.99), +(107, 48, 96, 2, 16.11), +(108, 48, 13, 1, 11.93), +(109, 49, 84, 2, 39.32), +(110, 50, 89, 1, 74.88), +(111, 50, 6, 2, 52.20), +(112, 51, 94, 2, 12.64), +(113, 51, 11, 1, 7.49), +(114, 51, 28, 2, 41.38), +(115, 52, 99, 1, 21.31), +(116, 53, 4, 2, 41.36), +(117, 53, 21, 1, 10.89), +(118, 54, 9, 1, 68.47), +(119, 54, 26, 2, 32.67), +(120, 54, 43, 1, 22.68), +(121, 55, 14, 2, 14.16), +(122, 56, 19, 1, 25.27), +(123, 56, 36, 2, 84.85), +(124, 57, 24, 2, 23.96), +(125, 57, 41, 1, 11.04), +(126, 57, 58, 2, 45.82), +(127, 58, 29, 1, 45.73), +(128, 59, 34, 2, 59.42), +(129, 59, 51, 1, 14.09), +(130, 60, 39, 1, 122.98), +(131, 60, 56, 2, 36.76), +(132, 60, 73, 1, 69.99), +(133, 61, 44, 2, 28.51), +(134, 62, 49, 1, 57.62), +(135, 62, 66, 2, 22.69), +(136, 63, 54, 2, 27.69), +(137, 63, 71, 1, 37.99), +(138, 63, 88, 2, 67.77), +(139, 64, 59, 1, 50.36), +(140, 65, 64, 2, 17.89), +(141, 65, 81, 1, 17.99), +(142, 66, 69, 1, 29.89), +(143, 66, 86, 2, 53.55), +(144, 66, 3, 1, 35.93), +(145, 67, 74, 2, 85.99), +(146, 68, 79, 1, 165.99), +(147, 68, 96, 2, 16.11), +(148, 69, 84, 2, 39.32), +(149, 69, 1, 1, 25.09), +(150, 69, 18, 2, 23.05), +(151, 70, 89, 1, 74.88), +(152, 71, 94, 2, 12.64), +(153, 71, 11, 1, 7.49), +(154, 72, 99, 1, 21.31), +(155, 72, 16, 2, 18.60), +(156, 72, 33, 1, 46.71), +(157, 73, 4, 2, 41.36), +(158, 74, 9, 1, 68.47), +(159, 74, 26, 2, 32.67), +(160, 75, 14, 2, 14.16), +(161, 75, 31, 1, 21.29), +(162, 75, 48, 2, 51.80), +(163, 76, 19, 1, 25.27), +(164, 77, 24, 2, 23.96), +(165, 77, 41, 1, 11.04), +(166, 78, 29, 1, 45.73), +(167, 78, 46, 2, 40.15), +(168, 78, 63, 1, 15.49), +(169, 79, 34, 2, 59.42), +(170, 80, 39, 1, 122.98), +(171, 80, 56, 2, 36.76), +(172, 81, 44, 2, 28.51), +(173, 81, 61, 1, 10.69), +(174, 81, 78, 2, 149.99), +(175, 82, 49, 1, 57.62), +(176, 83, 54, 2, 27.69), +(177, 83, 71, 1, 37.99), +(178, 84, 59, 1, 50.36), +(179, 84, 76, 2, 117.99), +(180, 84, 93, 1, 10.91), +(181, 85, 64, 2, 17.89), +(182, 86, 69, 1, 29.89), +(183, 86, 86, 2, 53.55), +(184, 87, 74, 2, 85.99), +(185, 87, 91, 1, 7.44), +(186, 87, 8, 2, 63.05), +(187, 88, 79, 1, 165.99), +(188, 89, 84, 2, 39.32), +(189, 89, 1, 1, 25.09), +(190, 90, 89, 1, 74.88), +(191, 90, 6, 2, 52.20), +(192, 90, 23, 1, 19.60), +(193, 91, 94, 2, 12.64), +(194, 92, 99, 1, 21.31), +(195, 92, 16, 2, 18.60), +(196, 93, 4, 2, 41.36), +(197, 93, 21, 1, 10.89), +(198, 93, 38, 2, 110.27), +(199, 94, 9, 1, 68.47), +(200, 95, 14, 2, 14.16), +(201, 95, 31, 1, 21.29), +(202, 96, 1, 1, 25.09), +(203, 96, 12, 1, 9.71), +(204, 97, 8, 1, 63.05), +(205, 97, 19, 1, 25.27), +(206, 98, 15, 1, 16.38), +(207, 98, 26, 1, 32.67), +(208, 99, 22, 1, 15.25), +(209, 100, 29, 1, 45.73), +(210, 100, 40, 1, 135.69), +(211, 101, 36, 1, 84.85), +(212, 101, 47, 1, 45.97), +(213, 102, 43, 1, 22.68), +(214, 102, 54, 1, 27.69), +(215, 103, 50, 1, 63.44), +(216, 103, 61, 1, 10.69), +(217, 104, 57, 1, 41.29), +(218, 104, 68, 1, 27.49), +(219, 105, 64, 1, 17.89), +(220, 106, 71, 1, 37.99), +(221, 106, 82, 1, 25.10), +(222, 107, 78, 1, 149.99), +(223, 108, 4, 1, 41.36), +(224, 109, 9, 1, 68.47), +(225, 110, 14, 1, 14.16); + +INSERT INTO refund (id, saleId, customerId, amount, reason, status) VALUES +(1, 1, 3, 34.80, 'Product was unsuitable after purchase.', 'APPROVED'), +(2, 2, 4, 88.32, 'Duplicate item purchased in error.', 'APPROVED'), +(3, 3, 5, 49.05, 'Pet outgrew the item sooner than expected.', 'APPROVED'), +(4, 4, 6, 15.25, 'Food sensitivity required a different formula.', 'APPROVED'), +(5, 5, 7, 181.42, 'Customer reported damaged packaging.', 'APPROVED'), +(6, 6, 8, 130.82, 'Product was unsuitable after purchase.', 'APPROVED'), +(7, 7, 9, 50.37, 'Duplicate item purchased in error.', 'APPROVED'), +(8, 8, 10, 74.13, 'Pet outgrew the item sooner than expected.', 'APPROVED'), +(9, 9, 11, 68.78, 'Food sensitivity required a different formula.', 'APPROVED'), +(10, 10, 12, 17.89, 'Customer reported damaged packaging.', 'APPROVED'), +(11, 11, 13, 63.09, 'Product was unsuitable after purchase.', 'APPROVED'), +(12, 12, 14, 149.99, 'Duplicate item purchased in error.', 'APPROVED'), +(13, 13, 15, 41.36, 'Pet outgrew the item sooner than expected.', 'APPROVED'), +(14, 14, 16, 68.47, 'Food sensitivity required a different formula.', 'APPROVED'), +(15, 15, 17, 14.16, 'Customer reported damaged packaging.', 'APPROVED'); + +INSERT INTO refund_item (id, refund_id, prod_id, quantity, unit_price) VALUES +(1, 1, 1, 1, 25.09), +(2, 1, 12, 1, 9.71), +(3, 2, 8, 1, 63.05), +(4, 2, 19, 1, 25.27), +(5, 3, 15, 1, 16.38), +(6, 3, 26, 1, 32.67), +(7, 4, 22, 1, 15.25), +(8, 5, 29, 1, 45.73), +(9, 5, 40, 1, 135.69), +(10, 6, 36, 1, 84.85), +(11, 6, 47, 1, 45.97), +(12, 7, 43, 1, 22.68), +(13, 7, 54, 1, 27.69), +(14, 8, 50, 1, 63.44), +(15, 8, 61, 1, 10.69), +(16, 9, 57, 1, 41.29), +(17, 9, 68, 1, 27.49), +(18, 10, 64, 1, 17.89), +(19, 11, 71, 1, 37.99), +(20, 11, 82, 1, 25.10), +(21, 12, 78, 1, 149.99), +(22, 13, 4, 1, 41.36), +(23, 14, 9, 1, 68.47), +(24, 15, 14, 1, 14.16); + +INSERT INTO conversation (id, customerId, staffId, status, mode, humanRequestedAt) VALUES +(1, 16, 3, 'CLOSED', 'AUTOMATED', NULL), +(2, 17, 4, 'OPEN', 'HUMAN', '2026-02-02 09:08:00'), +(3, 18, 5, 'OPEN', 'HUMAN', '2026-02-03 09:08:00'), +(4, 19, 6, 'OPEN', 'AUTOMATED', NULL), +(5, 20, 7, 'CLOSED', 'HUMAN', '2026-02-05 09:08:00'), +(6, 21, 8, 'OPEN', 'HUMAN', '2026-02-06 09:08:00'), +(7, 22, 9, 'OPEN', 'AUTOMATED', NULL), +(8, 23, 10, 'OPEN', 'HUMAN', '2026-02-08 09:08:00'), +(9, 24, 11, 'CLOSED', 'HUMAN', '2026-02-09 09:08:00'), +(10, 25, 12, 'OPEN', 'AUTOMATED', NULL), +(11, 26, 13, 'OPEN', 'HUMAN', '2026-02-11 09:08:00'), +(12, 27, 14, 'OPEN', 'HUMAN', '2026-02-12 09:08:00'), +(13, 28, 3, 'CLOSED', 'AUTOMATED', NULL), +(14, 29, 4, 'OPEN', 'HUMAN', '2026-02-14 09:08:00'), +(15, 30, 5, 'OPEN', 'HUMAN', '2026-02-15 09:08:00'), +(16, 31, 6, 'OPEN', 'AUTOMATED', NULL), +(17, 32, 7, 'CLOSED', 'HUMAN', '2026-02-17 09:08:00'), +(18, 33, 8, 'OPEN', 'HUMAN', '2026-02-18 09:08:00'), +(19, 34, 9, 'OPEN', 'AUTOMATED', NULL), +(20, 35, 10, 'OPEN', 'HUMAN', '2026-02-20 09:08:00'), +(21, 36, 11, 'CLOSED', 'HUMAN', '2026-02-21 09:08:00'), +(22, 37, 12, 'OPEN', 'AUTOMATED', NULL), +(23, 38, 13, 'OPEN', 'HUMAN', '2026-02-23 09:08:00'), +(24, 39, 14, 'OPEN', 'HUMAN', '2026-02-24 09:08:00'), +(25, 40, 3, 'CLOSED', 'AUTOMATED', NULL), +(26, 41, 4, 'OPEN', 'HUMAN', '2026-02-26 09:08:00'), +(27, 42, 5, 'OPEN', 'HUMAN', '2026-02-27 09:08:00'), +(28, 43, 6, 'OPEN', 'AUTOMATED', NULL), +(29, 44, 7, 'CLOSED', 'HUMAN', '2026-03-01 09:08:00'), +(30, 45, 8, 'OPEN', 'HUMAN', '2026-03-02 09:08:00'); + +INSERT INTO message (id, conversationId, senderId, content, attachmentUrl, attachmentName, attachmentMimeType, attachmentSizeBytes, timestamp, isRead) VALUES +(1, 1, 16, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-01 09:00:00', 1), +(2, 1, 3, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-01 09:05:00', 1), +(3, 1, 16, 'Order #1001 is the one I meant, and the pet is Bruno.', 'https://files.petshop.local/chat/001-2.pdf', 'order-note-001.pdf', 'application/pdf', 145000, '2026-02-01 09:10:00', 1), +(4, 1, 3, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-01 09:15:00', 0), +(5, 2, 17, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-02 09:00:00', 1), +(6, 2, 4, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-02 09:05:00', 1), +(7, 2, 17, 'Order #1002 is the one I meant, and the pet is Snowball.', NULL, NULL, NULL, NULL, '2026-02-02 09:10:00', 1), +(8, 2, 4, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-02 09:15:00', 0), +(9, 3, 18, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-03 09:00:00', 1), +(10, 3, 5, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-03 09:05:00', 1), +(11, 3, 18, 'Order #1003 is the one I meant, and the pet is Zeus.', NULL, NULL, NULL, NULL, '2026-02-03 09:10:00', 1), +(12, 3, 5, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-03 09:15:00', 0), +(13, 4, 19, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-04 09:00:00', 1), +(14, 4, 6, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-04 09:05:00', 1), +(15, 4, 19, 'Order #1004 is the one I meant, and the pet is Biscuit.', NULL, NULL, NULL, NULL, '2026-02-04 09:10:00', 1), +(16, 4, 6, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-04 09:15:00', 0), +(17, 5, 20, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-05 09:00:00', 1), +(18, 5, 7, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-05 09:05:00', 1), +(19, 5, 20, 'Order #1005 is the one I meant, and the pet is Patches.', NULL, NULL, NULL, NULL, '2026-02-05 09:10:00', 1), +(20, 5, 7, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-05 09:15:00', 0), +(21, 6, 21, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-06 09:00:00', 1), +(22, 6, 8, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-06 09:05:00', 1), +(23, 6, 21, 'Order #1006 is the one I meant, and the pet is Scout.', 'https://files.petshop.local/chat/006-2.pdf', 'order-note-006.pdf', 'application/pdf', 145500, '2026-02-06 09:10:00', 1), +(24, 6, 8, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-06 09:15:00', 0), +(25, 7, 22, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-07 09:00:00', 1), +(26, 7, 9, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-07 09:05:00', 1), +(27, 7, 22, 'Order #1007 is the one I meant, and the pet is Mittens.', NULL, NULL, NULL, NULL, '2026-02-07 09:10:00', 1), +(28, 7, 9, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-07 09:15:00', 0), +(29, 8, 23, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-08 09:00:00', 1), +(30, 8, 10, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-08 09:05:00', 1), +(31, 8, 23, 'Order #1008 is the one I meant, and the pet is Thor.', NULL, NULL, NULL, NULL, '2026-02-08 09:10:00', 1), +(32, 8, 10, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-08 09:15:00', 0), +(33, 9, 24, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-09 09:00:00', 1), +(34, 9, 11, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-09 09:05:00', 1), +(35, 9, 24, 'Order #1009 is the one I meant, and the pet is Whiskers.', NULL, NULL, NULL, NULL, '2026-02-09 09:10:00', 1), +(36, 9, 11, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-09 09:15:00', 0), +(37, 10, 25, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-10 09:00:00', 1), +(38, 10, 12, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-10 09:05:00', 1), +(39, 10, 25, 'Order #1010 is the one I meant, and the pet is Goldie.', NULL, NULL, NULL, NULL, '2026-02-10 09:10:00', 1), +(40, 10, 12, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-10 09:15:00', 0), +(41, 11, 26, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-11 09:00:00', 1), +(42, 11, 13, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-11 09:05:00', 1), +(43, 11, 26, 'Order #1011 is the one I meant, and the pet is Midnight.', 'https://files.petshop.local/chat/011-2.pdf', 'order-note-011.pdf', 'application/pdf', 146000, '2026-02-11 09:10:00', 1), +(44, 11, 13, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-11 09:15:00', 0), +(45, 12, 27, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-12 09:00:00', 1), +(46, 12, 14, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-12 09:05:00', 1), +(47, 12, 27, 'Order #1012 is the one I meant, and the pet is Storm.', NULL, NULL, NULL, NULL, '2026-02-12 09:10:00', 1), +(48, 12, 14, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-12 09:15:00', 0), +(49, 13, 28, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-13 09:00:00', 1), +(50, 13, 3, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-13 09:05:00', 1), +(51, 13, 28, 'Order #1013 is the one I meant, and the pet is Peanut.', NULL, NULL, NULL, NULL, '2026-02-13 09:10:00', 1), +(52, 13, 3, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-13 09:15:00', 0), +(53, 14, 29, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-14 09:00:00', 1), +(54, 14, 4, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-14 09:05:00', 1), +(55, 14, 29, 'Order #1014 is the one I meant, and the pet is Daisy.', NULL, NULL, NULL, NULL, '2026-02-14 09:10:00', 1), +(56, 14, 4, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-14 09:15:00', 0), +(57, 15, 30, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-15 09:00:00', 1), +(58, 15, 5, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-15 09:05:00', 1), +(59, 15, 30, 'Order #1015 is the one I meant, and the pet is Cleo.', NULL, NULL, NULL, NULL, '2026-02-15 09:10:00', 1), +(60, 15, 5, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-15 09:15:00', 0), +(61, 16, 31, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-16 09:00:00', 1), +(62, 16, 6, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-16 09:05:00', 1), +(63, 16, 31, 'Order #1016 is the one I meant, and the pet is Sunny.', 'https://files.petshop.local/chat/016-2.pdf', 'order-note-016.pdf', 'application/pdf', 146500, '2026-02-16 09:10:00', 1), +(64, 16, 6, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-16 09:15:00', 0), +(65, 17, 32, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-17 09:00:00', 1), +(66, 17, 7, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-17 09:05:00', 1), +(67, 17, 32, 'Order #1017 is the one I meant, and the pet is Maple.', NULL, NULL, NULL, NULL, '2026-02-17 09:10:00', 1), +(68, 17, 7, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-17 09:15:00', 0), +(69, 18, 33, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-18 09:00:00', 1), +(70, 18, 8, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-18 09:05:00', 1), +(71, 18, 33, 'Order #1018 is the one I meant, and the pet is Nova.', NULL, NULL, NULL, NULL, '2026-02-18 09:10:00', 1), +(72, 18, 8, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-18 09:15:00', 0), +(73, 19, 34, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-19 09:00:00', 1), +(74, 19, 9, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-19 09:05:00', 1), +(75, 19, 34, 'Order #1019 is the one I meant, and the pet is Piper.', NULL, NULL, NULL, NULL, '2026-02-19 09:10:00', 1), +(76, 19, 9, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-19 09:15:00', 0), +(77, 20, 35, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-20 09:00:00', 1), +(78, 20, 10, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-20 09:05:00', 1), +(79, 20, 35, 'Order #1020 is the one I meant, and the pet is Hazel.', NULL, NULL, NULL, NULL, '2026-02-20 09:10:00', 1), +(80, 20, 10, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-20 09:15:00', 0), +(81, 21, 36, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-21 09:00:00', 1), +(82, 21, 11, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-21 09:05:00', 1), +(83, 21, 36, 'Order #1021 is the one I meant, and the pet is Jasper.', 'https://files.petshop.local/chat/021-2.pdf', 'order-note-021.pdf', 'application/pdf', 147000, '2026-02-21 09:10:00', 1), +(84, 21, 11, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-21 09:15:00', 0), +(85, 22, 37, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-22 09:00:00', 1), +(86, 22, 12, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-22 09:05:00', 1), +(87, 22, 37, 'Order #1022 is the one I meant, and the pet is Remy.', NULL, NULL, NULL, NULL, '2026-02-22 09:10:00', 1), +(88, 22, 12, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-22 09:15:00', 0), +(89, 23, 38, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-23 09:00:00', 1), +(90, 23, 13, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-23 09:05:00', 1), +(91, 23, 38, 'Order #1023 is the one I meant, and the pet is Archie.', NULL, NULL, NULL, NULL, '2026-02-23 09:10:00', 1), +(92, 23, 13, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-23 09:15:00', 0), +(93, 24, 39, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-24 09:00:00', 1), +(94, 24, 14, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-24 09:05:00', 1), +(95, 24, 39, 'Order #1024 is the one I meant, and the pet is Skye.', NULL, NULL, NULL, NULL, '2026-02-24 09:10:00', 1), +(96, 24, 14, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-24 09:15:00', 0), +(97, 25, 40, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-25 09:00:00', 1), +(98, 25, 3, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-25 09:05:00', 1), +(99, 25, 40, 'Order #1025 is the one I meant, and the pet is Otis.', NULL, NULL, NULL, NULL, '2026-02-25 09:10:00', 1), +(100, 25, 3, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-25 09:15:00', 0), +(101, 26, 41, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-26 09:00:00', 1), +(102, 26, 4, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-26 09:05:00', 1), +(103, 26, 41, 'Order #1026 is the one I meant, and the pet is Marley.', 'https://files.petshop.local/chat/026-2.pdf', 'order-note-026.pdf', 'application/pdf', 147500, '2026-02-26 09:10:00', 1), +(104, 26, 4, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-26 09:15:00', 0), +(105, 27, 42, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-27 09:00:00', 1), +(106, 27, 5, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-27 09:05:00', 1), +(107, 27, 42, 'Order #1027 is the one I meant, and the pet is Blue.', NULL, NULL, NULL, NULL, '2026-02-27 09:10:00', 1), +(108, 27, 5, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-27 09:15:00', 0), +(109, 28, 43, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-02-28 09:00:00', 1), +(110, 28, 6, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-02-28 09:05:00', 1), +(111, 28, 43, 'Order #1028 is the one I meant, and the pet is Honey.', NULL, NULL, NULL, NULL, '2026-02-28 09:10:00', 1), +(112, 28, 6, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-02-28 09:15:00', 0), +(113, 29, 44, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-03-01 09:00:00', 1), +(114, 29, 7, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-03-01 09:05:00', 1), +(115, 29, 44, 'Order #1029 is the one I meant, and the pet is Mochi.', NULL, NULL, NULL, NULL, '2026-03-01 09:10:00', 1), +(116, 29, 7, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-03-01 09:15:00', 0), +(117, 30, 45, 'Hi, I need help with a recent order and a pet care question.', NULL, NULL, NULL, NULL, '2026-03-02 09:00:00', 1), +(118, 30, 8, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-03-02 09:05:00', 1), +(119, 30, 45, 'Order #1030 is the one I meant, and the pet is Kiki.', NULL, NULL, NULL, NULL, '2026-03-02 09:10:00', 1), +(120, 30, 8, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-03-02 09:15:00', 0); + +INSERT INTO activityLog (logId, userId, storeId, activity, logTimestamp) VALUES +(1, 1, 1, 'Reviewed store inventory adjustments.', '2026-01-03 08:00:00'), +(2, 2, 2, 'Approved a purchase transaction at the register.', '2026-01-03 17:00:00'), +(3, 3, 1, 'Updated a pet availability record.', '2026-01-04 02:00:00'), +(4, 4, 1, 'Completed a grooming appointment handoff.', '2026-01-04 11:00:00'), +(5, 5, 1, 'Checked a pending adoption record.', '2026-01-04 20:00:00'), +(6, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-05 05:00:00'), +(7, 7, 2, 'Answered a customer support conversation.', '2026-01-05 14:00:00'), +(8, 8, 2, 'Updated a product detail for the catalogue.', '2026-01-05 23:00:00'), +(9, 9, 2, 'Reviewed store inventory adjustments.', '2026-01-06 08:00:00'), +(10, 10, 2, 'Approved a purchase transaction at the register.', '2026-01-06 17:00:00'), +(11, 11, 3, 'Updated a pet availability record.', '2026-01-07 02:00:00'), +(12, 12, 3, 'Completed a grooming appointment handoff.', '2026-01-07 11:00:00'), +(13, 13, 3, 'Checked a pending adoption record.', '2026-01-07 20:00:00'), +(14, 14, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-08 05:00:00'), +(15, 1, 1, 'Answered a customer support conversation.', '2026-01-08 14:00:00'), +(16, 2, 2, 'Updated a product detail for the catalogue.', '2026-01-08 23:00:00'), +(17, 3, 1, 'Reviewed store inventory adjustments.', '2026-01-09 08:00:00'), +(18, 4, 1, 'Approved a purchase transaction at the register.', '2026-01-09 17:00:00'), +(19, 5, 1, 'Updated a pet availability record.', '2026-01-10 02:00:00'), +(20, 6, 1, 'Completed a grooming appointment handoff.', '2026-01-10 11:00:00'), +(21, 7, 2, 'Checked a pending adoption record.', '2026-01-10 20:00:00'), +(22, 8, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-11 05:00:00'), +(23, 9, 2, 'Answered a customer support conversation.', '2026-01-11 14:00:00'), +(24, 10, 2, 'Updated a product detail for the catalogue.', '2026-01-11 23:00:00'), +(25, 11, 3, 'Reviewed store inventory adjustments.', '2026-01-12 08:00:00'), +(26, 12, 3, 'Approved a purchase transaction at the register.', '2026-01-12 17:00:00'), +(27, 13, 3, 'Updated a pet availability record.', '2026-01-13 02:00:00'), +(28, 14, 3, 'Completed a grooming appointment handoff.', '2026-01-13 11:00:00'), +(29, 1, 1, 'Checked a pending adoption record.', '2026-01-13 20:00:00'), +(30, 2, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-14 05:00:00'), +(31, 3, 1, 'Answered a customer support conversation.', '2026-01-14 14:00:00'), +(32, 4, 1, 'Updated a product detail for the catalogue.', '2026-01-14 23:00:00'), +(33, 5, 1, 'Reviewed store inventory adjustments.', '2026-01-15 08:00:00'), +(34, 6, 1, 'Approved a purchase transaction at the register.', '2026-01-15 17:00:00'), +(35, 7, 2, 'Updated a pet availability record.', '2026-01-16 02:00:00'), +(36, 8, 2, 'Completed a grooming appointment handoff.', '2026-01-16 11:00:00'), +(37, 9, 2, 'Checked a pending adoption record.', '2026-01-16 20:00:00'), +(38, 10, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-17 05:00:00'), +(39, 11, 3, 'Answered a customer support conversation.', '2026-01-17 14:00:00'), +(40, 12, 3, 'Updated a product detail for the catalogue.', '2026-01-17 23:00:00'), +(41, 13, 3, 'Reviewed store inventory adjustments.', '2026-01-18 08:00:00'), +(42, 14, 3, 'Approved a purchase transaction at the register.', '2026-01-18 17:00:00'), +(43, 1, 1, 'Updated a pet availability record.', '2026-01-19 02:00:00'), +(44, 2, 2, 'Completed a grooming appointment handoff.', '2026-01-19 11:00:00'), +(45, 3, 1, 'Checked a pending adoption record.', '2026-01-19 20:00:00'), +(46, 4, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-20 05:00:00'), +(47, 5, 1, 'Answered a customer support conversation.', '2026-01-20 14:00:00'), +(48, 6, 1, 'Updated a product detail for the catalogue.', '2026-01-20 23:00:00'), +(49, 7, 2, 'Reviewed store inventory adjustments.', '2026-01-21 08:00:00'), +(50, 8, 2, 'Approved a purchase transaction at the register.', '2026-01-21 17:00:00'), +(51, 9, 2, 'Updated a pet availability record.', '2026-01-22 02:00:00'), +(52, 10, 2, 'Completed a grooming appointment handoff.', '2026-01-22 11:00:00'), +(53, 11, 3, 'Checked a pending adoption record.', '2026-01-22 20:00:00'), +(54, 12, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-23 05:00:00'), +(55, 13, 3, 'Answered a customer support conversation.', '2026-01-23 14:00:00'), +(56, 14, 3, 'Updated a product detail for the catalogue.', '2026-01-23 23:00:00'), +(57, 1, 1, 'Reviewed store inventory adjustments.', '2026-01-24 08:00:00'), +(58, 2, 2, 'Approved a purchase transaction at the register.', '2026-01-24 17:00:00'), +(59, 3, 1, 'Updated a pet availability record.', '2026-01-25 02:00:00'), +(60, 4, 1, 'Completed a grooming appointment handoff.', '2026-01-25 11:00:00'), +(61, 5, 1, 'Checked a pending adoption record.', '2026-01-25 20:00:00'), +(62, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-26 05:00:00'), +(63, 7, 2, 'Answered a customer support conversation.', '2026-01-26 14:00:00'), +(64, 8, 2, 'Updated a product detail for the catalogue.', '2026-01-26 23:00:00'), +(65, 9, 2, 'Reviewed store inventory adjustments.', '2026-01-27 08:00:00'), +(66, 10, 2, 'Approved a purchase transaction at the register.', '2026-01-27 17:00:00'), +(67, 11, 3, 'Updated a pet availability record.', '2026-01-28 02:00:00'), +(68, 12, 3, 'Completed a grooming appointment handoff.', '2026-01-28 11:00:00'), +(69, 13, 3, 'Checked a pending adoption record.', '2026-01-28 20:00:00'), +(70, 14, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-29 05:00:00'), +(71, 1, 1, 'Answered a customer support conversation.', '2026-01-29 14:00:00'), +(72, 2, 2, 'Updated a product detail for the catalogue.', '2026-01-29 23:00:00'), +(73, 3, 1, 'Reviewed store inventory adjustments.', '2026-01-30 08:00:00'), +(74, 4, 1, 'Approved a purchase transaction at the register.', '2026-01-30 17:00:00'), +(75, 5, 1, 'Updated a pet availability record.', '2026-01-31 02:00:00'), +(76, 6, 1, 'Completed a grooming appointment handoff.', '2026-01-31 11:00:00'), +(77, 7, 2, 'Checked a pending adoption record.', '2026-01-31 20:00:00'), +(78, 8, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-01 05:00:00'), +(79, 9, 2, 'Answered a customer support conversation.', '2026-02-01 14:00:00'), +(80, 10, 2, 'Updated a product detail for the catalogue.', '2026-02-01 23:00:00'), +(81, 11, 3, 'Reviewed store inventory adjustments.', '2026-02-02 08:00:00'), +(82, 12, 3, 'Approved a purchase transaction at the register.', '2026-02-02 17:00:00'), +(83, 13, 3, 'Updated a pet availability record.', '2026-02-03 02:00:00'), +(84, 14, 3, 'Completed a grooming appointment handoff.', '2026-02-03 11:00:00'), +(85, 1, 1, 'Checked a pending adoption record.', '2026-02-03 20:00:00'), +(86, 2, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-04 05:00:00'), +(87, 3, 1, 'Answered a customer support conversation.', '2026-02-04 14:00:00'), +(88, 4, 1, 'Updated a product detail for the catalogue.', '2026-02-04 23:00:00'), +(89, 5, 1, 'Reviewed store inventory adjustments.', '2026-02-05 08:00:00'), +(90, 6, 1, 'Approved a purchase transaction at the register.', '2026-02-05 17:00:00'), +(91, 7, 2, 'Updated a pet availability record.', '2026-02-06 02:00:00'), +(92, 8, 2, 'Completed a grooming appointment handoff.', '2026-02-06 11:00:00'), +(93, 9, 2, 'Checked a pending adoption record.', '2026-02-06 20:00:00'), +(94, 10, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-07 05:00:00'), +(95, 11, 3, 'Answered a customer support conversation.', '2026-02-07 14:00:00'), +(96, 12, 3, 'Updated a product detail for the catalogue.', '2026-02-07 23:00:00'), +(97, 13, 3, 'Reviewed store inventory adjustments.', '2026-02-08 08:00:00'), +(98, 14, 3, 'Approved a purchase transaction at the register.', '2026-02-08 17:00:00'), +(99, 1, 1, 'Updated a pet availability record.', '2026-02-09 02:00:00'), +(100, 2, 2, 'Completed a grooming appointment handoff.', '2026-02-09 11:00:00'), +(101, 3, 1, 'Checked a pending adoption record.', '2026-02-09 20:00:00'), +(102, 4, 1, 'Reviewed a refund request tied to an original sale.', '2026-02-10 05:00:00'), +(103, 5, 1, 'Answered a customer support conversation.', '2026-02-10 14:00:00'), +(104, 6, 1, 'Updated a product detail for the catalogue.', '2026-02-10 23:00:00'), +(105, 7, 2, 'Reviewed store inventory adjustments.', '2026-02-11 08:00:00'), +(106, 8, 2, 'Approved a purchase transaction at the register.', '2026-02-11 17:00:00'), +(107, 9, 2, 'Updated a pet availability record.', '2026-02-12 02:00:00'), +(108, 10, 2, 'Completed a grooming appointment handoff.', '2026-02-12 11:00:00'), +(109, 11, 3, 'Checked a pending adoption record.', '2026-02-12 20:00:00'), +(110, 12, 3, 'Reviewed a refund request tied to an original sale.', '2026-02-13 05:00:00'), +(111, 13, 3, 'Answered a customer support conversation.', '2026-02-13 14:00:00'), +(112, 14, 3, 'Updated a product detail for the catalogue.', '2026-02-13 23:00:00'), +(113, 1, 1, 'Reviewed store inventory adjustments.', '2026-02-14 08:00:00'), +(114, 2, 2, 'Approved a purchase transaction at the register.', '2026-02-14 17:00:00'), +(115, 3, 1, 'Updated a pet availability record.', '2026-02-15 02:00:00'), +(116, 4, 1, 'Completed a grooming appointment handoff.', '2026-02-15 11:00:00'), +(117, 5, 1, 'Checked a pending adoption record.', '2026-02-15 20:00:00'), +(118, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-02-16 05:00:00'), +(119, 7, 2, 'Answered a customer support conversation.', '2026-02-16 14:00:00'), +(120, 8, 2, 'Updated a product detail for the catalogue.', '2026-02-16 23:00:00'); -- 2.49.1 From 24b11e4152e7e195555f76330b2cc7f1fc9f7650 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 19:47:18 -0600 Subject: [PATCH 02/15] switch to target DB config --- backend/src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index c9ba26d3..98aac596 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -9,7 +9,7 @@ spring: max-request-size: 5MB datasource: - url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC} + url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3307/Petstoredb_target?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC} username: ${SPRING_DATASOURCE_USERNAME:petshop} password: ${SPRING_DATASOURCE_PASSWORD:petshop} driver-class-name: com.mysql.cj.jdbc.Driver @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: validate + ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} -- 2.49.1 From 824ed7e5eb830f30b83a6e8a4674970d261a9657 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 19:49:38 -0600 Subject: [PATCH 03/15] expand User entity fields --- .../petshop/backend/dto/user/UserRequest.java | 67 +++++++++++---- .../backend/dto/user/UserResponse.java | 74 +++++++++++------ .../java/com/petshop/backend/entity/User.java | 83 ++++++++++++++----- .../petshop/backend/service/UserService.java | 38 +++++++-- 4 files changed, 191 insertions(+), 71 deletions(-) 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 09a9036d..a6eb61cb 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 @@ -8,14 +8,20 @@ import jakarta.validation.constraints.Size; import java.util.Objects; public class UserRequest { - @NotBlank(message = "Username is required") @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") private String username; @Size(min = 6, message = "Password must be at least 6 characters") private String password; - @NotBlank(message = "Full name is required") + @NotBlank(message = "First name is required") + @Size(max = 50) + private String firstName; + + @NotBlank(message = "Last name is required") + @Size(max = 50) + private String lastName; + private String fullName; @Email(message = "Invalid email format") @@ -27,6 +33,10 @@ public class UserRequest { @NotNull(message = "Role is required") private User.Role role; + private String staffRole; + + private Long primaryStoreId; + private Boolean active = true; public String getUsername() { @@ -45,6 +55,22 @@ public class UserRequest { this.password = password; } + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getFullName() { return fullName; } @@ -77,6 +103,22 @@ public class UserRequest { this.role = role; } + public String getStaffRole() { + return staffRole; + } + + public void setStaffRole(String staffRole) { + this.staffRole = staffRole; + } + + public Long getPrimaryStoreId() { + return primaryStoreId; + } + + public void setPrimaryStoreId(Long primaryStoreId) { + this.primaryStoreId = primaryStoreId; + } + public Boolean getActive() { return active; } @@ -91,29 +133,20 @@ public class UserRequest { if (o == null || getClass() != o.getClass()) return false; UserRequest that = (UserRequest) o; return Objects.equals(username, that.username) && - Objects.equals(password, that.password) && - Objects.equals(fullName, that.fullName) && + Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && - Objects.equals(phone, that.phone) && - role == that.role && - Objects.equals(active, that.active); + role == that.role; } @Override public int hashCode() { - return Objects.hash(username, password, fullName, email, phone, role, active); + return Objects.hash(username, firstName, lastName, email, role); } @Override public String toString() { - return "UserRequest{" + - "username='" + username + '\'' + - ", password='" + password + '\'' + - ", fullName='" + fullName + '\'' + - ", email='" + email + '\'' + - ", phone='" + phone + '\'' + - ", role=" + role + - ", active=" + active + - '}'; + return "UserRequest{username='" + username + "', firstName='" + firstName + + "', lastName='" + lastName + "', role=" + role + '}'; } } 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 9d7167c2..6c4d15b5 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 @@ -6,10 +6,15 @@ import java.util.Objects; public class UserResponse { private Long id; private String username; + private String firstName; + private String lastName; private String fullName; private String email; private String phone; private String role; + private String staffRole; + private Long primaryStoreId; + private Integer loyaltyPoints; private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -17,18 +22,6 @@ public class UserResponse { public UserResponse() { } - public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.username = username; - this.fullName = fullName; - this.email = email; - this.phone = phone; - this.role = role; - this.active = active; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getId() { return id; } @@ -45,6 +38,22 @@ public class UserResponse { this.username = username; } + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getFullName() { return fullName; } @@ -77,6 +86,30 @@ public class UserResponse { this.role = role; } + public String getStaffRole() { + return staffRole; + } + + public void setStaffRole(String staffRole) { + this.staffRole = staffRole; + } + + public Long getPrimaryStoreId() { + return primaryStoreId; + } + + public void setPrimaryStoreId(Long primaryStoreId) { + this.primaryStoreId = primaryStoreId; + } + + public Integer getLoyaltyPoints() { + return loyaltyPoints; + } + + public void setLoyaltyPoints(Integer loyaltyPoints) { + this.loyaltyPoints = loyaltyPoints; + } + public Boolean getActive() { return active; } @@ -106,26 +139,17 @@ public class UserResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserResponse that = (UserResponse) o; - return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(id, that.id); } @Override public int hashCode() { - return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt); + return Objects.hash(id); } @Override public String toString() { - return "UserResponse{" + - "id=" + id + - ", username='" + username + '\'' + - ", fullName='" + fullName + '\'' + - ", email='" + email + '\'' + - ", phone='" + phone + '\'' + - ", role='" + role + '\'' + - ", active=" + active + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; + return "UserResponse{id=" + id + ", username='" + username + "', firstName='" + firstName + + "', lastName='" + lastName + "', role='" + role + "', active=" + active + '}'; } } 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 d13e2e68..97b8dfc5 100644 --- a/backend/src/main/java/com/petshop/backend/entity/User.java +++ b/backend/src/main/java/com/petshop/backend/entity/User.java @@ -16,15 +16,21 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, unique = true, length = 50) + @Column(unique = true, length = 50) private String username; - @Column(nullable = false) + @Column private String password; @Column(unique = true, length = 100) private String email; + @Column(nullable = false, length = 50) + private String firstName; + + @Column(nullable = false, length = 50) + private String lastName; + @Column(length = 100) private String fullName; @@ -38,6 +44,16 @@ public class User { @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private Role role; + @Column(length = 50) + private String staffRole; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "primaryStoreId") + private StoreLocation primaryStore; + + @Column(nullable = false) + private Integer loyaltyPoints = 0; + @Column(nullable = false) private Boolean active = true; @@ -59,21 +75,6 @@ public class User { public User() { } - public User(Long id, String username, String password, String email, String fullName, String phone, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.username = username; - this.password = password; - this.email = email; - this.fullName = fullName; - this.phone = phone; - this.avatarUrl = avatarUrl; - this.role = role; - this.active = active; - this.tokenVersion = tokenVersion; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getId() { return id; } @@ -106,6 +107,22 @@ public class User { this.email = email; } + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getFullName() { return fullName; } @@ -138,6 +155,30 @@ public class User { this.role = role; } + public String getStaffRole() { + return staffRole; + } + + public void setStaffRole(String staffRole) { + this.staffRole = staffRole; + } + + public StoreLocation getPrimaryStore() { + return primaryStore; + } + + public void setPrimaryStore(StoreLocation primaryStore) { + this.primaryStore = primaryStore; + } + + public Integer getLoyaltyPoints() { + return loyaltyPoints; + } + + public void setLoyaltyPoints(Integer loyaltyPoints) { + this.loyaltyPoints = loyaltyPoints; + } + public Boolean getActive() { return active; } @@ -188,16 +229,12 @@ public class User { return "User{" + "id=" + id + ", username='" + username + '\'' + - ", password='" + password + '\'' + ", email='" + email + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + ", fullName='" + fullName + '\'' + - ", phone='" + phone + '\'' + - ", avatarUrl='" + avatarUrl + '\'' + ", role=" + role + ", active=" + active + - ", tokenVersion=" + tokenVersion + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + '}'; } } 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 3c219172..e3a17c5c 100644 --- a/backend/src/main/java/com/petshop/backend/service/UserService.java +++ b/backend/src/main/java/com/petshop/backend/service/UserService.java @@ -3,8 +3,10 @@ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.dto.user.UserRequest; import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.StoreRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -14,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; import java.util.Locale; +import java.util.Objects; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; @@ -24,11 +27,13 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final UserBusinessLinkageService userBusinessLinkageService; + private final StoreRepository storeRepository; - public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreRepository storeRepository) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.userBusinessLinkageService = userBusinessLinkageService; + this.storeRepository = storeRepository; } public Page getAllUsers(String query, String role, Pageable pageable) { @@ -56,12 +61,18 @@ public class UserService { @Transactional public UserResponse createUser(UserRequest request) { User user = new User(); - user.setUsername(request.getUsername()); - user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setUsername(trimToNull(request.getUsername())); + if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { + user.setPassword(passwordEncoder.encode(request.getPassword())); + } + user.setFirstName(request.getFirstName()); + user.setLastName(request.getLastName()); user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); user.setPhone(trimToNull(request.getPhone())); user.setRole(request.getRole()); + user.setStaffRole(trimToNull(request.getStaffRole())); + user.setPrimaryStore(resolveStore(request.getPrimaryStoreId())); user.setActive(request.getActive() != null ? request.getActive() : true); validateUniquePhone(user.getPhone(), null); @@ -79,23 +90,27 @@ public class UserService { .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); boolean invalidateToken = - !user.getUsername().equals(request.getUsername()) + !Objects.equals(user.getUsername(), request.getUsername()) || user.getRole() != request.getRole() || !user.getActive().equals(request.getActive() != null ? request.getActive() : true); - user.setUsername(request.getUsername()); + user.setUsername(trimToNull(request.getUsername())); if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); invalidateToken = true; } + user.setFirstName(request.getFirstName()); + user.setLastName(request.getLastName()); user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); String phone = trimToNull(request.getPhone()); - if (!java.util.Objects.equals(user.getPhone(), phone)) { + if (!Objects.equals(user.getPhone(), phone)) { validateUniquePhone(phone, user.getId()); } user.setPhone(phone); user.setRole(request.getRole()); + user.setStaffRole(trimToNull(request.getStaffRole())); + user.setPrimaryStore(resolveStore(request.getPrimaryStoreId())); user.setActive(request.getActive() != null ? request.getActive() : true); if (invalidateToken) { user.setTokenVersion(user.getTokenVersion() + 1); @@ -123,16 +138,27 @@ public class UserService { UserResponse response = new UserResponse(); response.setId(user.getId()); response.setUsername(user.getUsername()); + response.setFirstName(user.getFirstName()); + response.setLastName(user.getLastName()); response.setFullName(user.getFullName()); response.setEmail(user.getEmail()); response.setPhone(user.getPhone()); response.setRole(user.getRole().toString()); + response.setStaffRole(user.getStaffRole()); + response.setPrimaryStoreId(user.getPrimaryStore() != null ? user.getPrimaryStore().getStoreId() : null); + response.setLoyaltyPoints(user.getLoyaltyPoints()); response.setActive(user.getActive()); response.setCreatedAt(user.getCreatedAt()); response.setUpdatedAt(user.getUpdatedAt()); return response; } + private StoreLocation resolveStore(Long storeId) { + if (storeId == null) return null; + return storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + } + private void validateUniquePhone(String phone, Long currentUserId) { if (phone == null || phone.isBlank()) { return; -- 2.49.1 From 2360dc2419474ac28858d0c5a9f2fac52856d189 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:17:27 -0600 Subject: [PATCH 04/15] merge customer/employee into users --- .../backend/config/DataInitializer.java | 59 +++--- ...calAppointmentCustomerSeedInitializer.java | 34 ---- .../controller/AdoptionController.java | 15 +- .../controller/AppointmentController.java | 19 +- .../backend/controller/AuthController.java | 83 ++------ .../backend/controller/ChatController.java | 5 +- .../controller/CustomerController.java | 34 ++-- .../controller/CustomerPetController.java | 118 ----------- .../controller/DropdownController.java | 63 +----- .../controller/EmployeeController.java | 35 ++-- .../backend/controller/RefundController.java | 19 +- .../dto/appointment/AppointmentRequest.java | 14 +- .../dto/appointment/AppointmentResponse.java | 20 -- .../backend/dto/customer/CustomerRequest.java | 64 ------ .../dto/customer/CustomerResponse.java | 98 ---------- .../dto/customerpet/CustomerPetRequest.java | 66 ------- .../dto/customerpet/CustomerPetResponse.java | 123 ------------ .../backend/dto/employee/EmployeeRequest.java | 51 ----- .../dto/employee/EmployeeResponse.java | 43 ---- .../petshop/backend/entity/ActivityLog.java | 18 +- .../com/petshop/backend/entity/Adoption.java | 31 +-- .../petshop/backend/entity/Appointment.java | 43 +--- .../com/petshop/backend/entity/Customer.java | 132 ------------- .../petshop/backend/entity/CustomerPet.java | 137 ------------- .../com/petshop/backend/entity/Employee.java | 158 --------------- .../petshop/backend/entity/EmployeeStore.java | 117 ----------- .../java/com/petshop/backend/entity/Pet.java | 61 ++---- .../java/com/petshop/backend/entity/Sale.java | 34 +--- .../repository/AdoptionRepository.java | 4 +- .../repository/AppointmentRepository.java | 12 +- .../repository/CustomerPetRepository.java | 18 -- .../repository/CustomerRepository.java | 29 --- .../repository/EmployeeRepository.java | 30 --- .../repository/EmployeeStoreRepository.java | 21 -- .../backend/repository/PetRepository.java | 8 +- .../backend/repository/UserRepository.java | 13 +- .../backend/service/AdoptionService.java | 47 ++--- .../backend/service/AnalyticsService.java | 15 +- .../backend/service/AppointmentService.java | 129 +++--------- .../backend/service/ChatRealtimeService.java | 14 +- .../petshop/backend/service/ChatService.java | 20 +- .../backend/service/CustomerPetService.java | 163 ---------------- .../backend/service/CustomerService.java | 158 --------------- .../backend/service/EmployeeService.java | 183 ------------------ .../petshop/backend/service/PetService.java | 35 ++-- .../backend/service/RefundService.java | 4 +- .../petshop/backend/service/SaleService.java | 26 +-- .../service/StoreAssignmentService.java | 34 ---- .../service/UserBusinessLinkageService.java | 152 --------------- .../petshop/backend/service/UserService.java | 7 +- .../backend/util/AuthenticationHelper.java | 16 -- 51 files changed, 286 insertions(+), 2546 deletions(-) delete mode 100644 backend/src/main/java/com/petshop/backend/config/LocalAppointmentCustomerSeedInitializer.java delete mode 100644 backend/src/main/java/com/petshop/backend/controller/CustomerPetController.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetRequest.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetResponse.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java delete mode 100644 backend/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java delete mode 100644 backend/src/main/java/com/petshop/backend/entity/Customer.java delete mode 100644 backend/src/main/java/com/petshop/backend/entity/CustomerPet.java delete mode 100644 backend/src/main/java/com/petshop/backend/entity/Employee.java delete mode 100644 backend/src/main/java/com/petshop/backend/entity/EmployeeStore.java delete mode 100644 backend/src/main/java/com/petshop/backend/repository/CustomerPetRepository.java delete mode 100644 backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java delete mode 100644 backend/src/main/java/com/petshop/backend/repository/EmployeeRepository.java delete mode 100644 backend/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java delete mode 100644 backend/src/main/java/com/petshop/backend/service/CustomerPetService.java delete mode 100644 backend/src/main/java/com/petshop/backend/service/CustomerService.java delete mode 100644 backend/src/main/java/com/petshop/backend/service/EmployeeService.java delete mode 100644 backend/src/main/java/com/petshop/backend/service/StoreAssignmentService.java delete mode 100644 backend/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java 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 4a8c7470..9809ddc6 100644 --- a/backend/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/backend/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -2,8 +2,6 @@ package com.petshop.backend.config; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.service.StoreAssignmentService; -import com.petshop.backend.service.UserBusinessLinkageService; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -13,14 +11,10 @@ public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; - private final UserBusinessLinkageService userBusinessLinkageService; - private final StoreAssignmentService storeAssignmentService; - public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreAssignmentService storeAssignmentService) { + public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; - this.userBusinessLinkageService = userBusinessLinkageService; - this.storeAssignmentService = storeAssignmentService; } @Override @@ -34,16 +28,25 @@ public class DataInitializer implements CommandLineRunner { admin.setUsername("admin"); admin.setPassword(passwordEncoder.encode("admin123")); admin.setEmail("admin@petshop.com"); + admin.setFirstName("Admin"); + admin.setLastName("User"); admin.setFullName("Admin User"); admin.setPhone("000-000-1000"); admin.setRole(User.Role.ADMIN); admin.setActive(true); - admin = userRepository.save(admin); + userRepository.save(admin); System.out.println("Admin user created successfully"); } else { System.out.println("Admin user already exists"); - // Normalize missing fields if needed boolean updated = false; + if (admin.getFirstName() == null || admin.getFirstName().isEmpty()) { + admin.setFirstName("Admin"); + updated = true; + } + if (admin.getLastName() == null || admin.getLastName().isEmpty()) { + admin.setLastName("User"); + updated = true; + } if (admin.getFullName() == null || admin.getFullName().isEmpty()) { admin.setFullName("Admin User"); updated = true; @@ -65,12 +68,10 @@ public class DataInitializer implements CommandLineRunner { updated = true; } if (updated) { - admin = userRepository.save(admin); + userRepository.save(admin); System.out.println("Admin user normalized"); } } - // Ensure linked employee - storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(admin), 1L); User staff = userRepository.findByUsername("staff").orElse(null); if (staff == null) { @@ -79,16 +80,25 @@ public class DataInitializer implements CommandLineRunner { staff.setUsername("staff"); staff.setPassword(passwordEncoder.encode("staff123")); staff.setEmail("staff@petshop.com"); + staff.setFirstName("Staff"); + staff.setLastName("User"); staff.setFullName("Staff User"); staff.setPhone("000-000-1001"); staff.setRole(User.Role.STAFF); staff.setActive(true); - staff = userRepository.save(staff); + userRepository.save(staff); System.out.println("Staff user created successfully"); } else { System.out.println("Staff user already exists"); - // Normalize missing fields if needed boolean updated = false; + if (staff.getFirstName() == null || staff.getFirstName().isEmpty()) { + staff.setFirstName("Staff"); + updated = true; + } + if (staff.getLastName() == null || staff.getLastName().isEmpty()) { + staff.setLastName("User"); + updated = true; + } if (staff.getFullName() == null || staff.getFullName().isEmpty()) { staff.setFullName("Staff User"); updated = true; @@ -110,12 +120,10 @@ public class DataInitializer implements CommandLineRunner { updated = true; } if (updated) { - staff = userRepository.save(staff); + userRepository.save(staff); System.out.println("Staff user normalized"); } } - // Ensure linked employee - storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(staff), 1L); User customer = userRepository.findByUsername("customer").orElse(null); if (customer == null) { @@ -124,16 +132,25 @@ public class DataInitializer implements CommandLineRunner { customer.setUsername("customer"); customer.setPassword(passwordEncoder.encode("customer123")); customer.setEmail("customer@petshop.com"); + customer.setFirstName("Test"); + customer.setLastName("Customer"); customer.setFullName("Test Customer"); customer.setPhone("000-000-1002"); customer.setRole(User.Role.CUSTOMER); customer.setActive(true); - customer = userRepository.save(customer); + userRepository.save(customer); System.out.println("Customer user created successfully"); } else { System.out.println("Customer user already exists"); - // Normalize missing fields if needed boolean updated = false; + if (customer.getFirstName() == null || customer.getFirstName().isEmpty()) { + customer.setFirstName("Test"); + updated = true; + } + if (customer.getLastName() == null || customer.getLastName().isEmpty()) { + customer.setLastName("Customer"); + updated = true; + } if (customer.getFullName() == null || customer.getFullName().isEmpty()) { customer.setFullName("Test Customer"); updated = true; @@ -155,12 +172,10 @@ public class DataInitializer implements CommandLineRunner { updated = true; } if (updated) { - customer = userRepository.save(customer); + userRepository.save(customer); System.out.println("Customer user normalized"); } } - // Ensure linked customer - userBusinessLinkageService.ensureLinkedCustomer(customer); System.out.println("==== DataInitializer: Completed ===="); } diff --git a/backend/src/main/java/com/petshop/backend/config/LocalAppointmentCustomerSeedInitializer.java b/backend/src/main/java/com/petshop/backend/config/LocalAppointmentCustomerSeedInitializer.java deleted file mode 100644 index 36b78fb4..00000000 --- a/backend/src/main/java/com/petshop/backend/config/LocalAppointmentCustomerSeedInitializer.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.petshop.backend.config; - -import com.petshop.backend.repository.CustomerPetRepository; -import org.springframework.boot.CommandLineRunner; -import org.springframework.context.annotation.Profile; -import org.springframework.core.io.ClassPathResource; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; - -@Component -@Profile("local") -public class LocalAppointmentCustomerSeedInitializer implements CommandLineRunner { - - private final DataSource dataSource; - private final CustomerPetRepository customerPetRepository; - - public LocalAppointmentCustomerSeedInitializer(DataSource dataSource, CustomerPetRepository customerPetRepository) { - this.dataSource = dataSource; - this.customerPetRepository = customerPetRepository; - } - - @Override - public void run(String... args) { - if (customerPetRepository.count() > 0) { - return; - } - - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(false, false, "UTF-8", - new ClassPathResource("dev/seed_demo_customer_pets.sql")); - populator.execute(dataSource); - } -} 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 41a2e815..bcb61db4 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -3,8 +3,7 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.adoption.AdoptionRequest; import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AdoptionService; import com.petshop.backend.util.AuthenticationHelper; @@ -24,12 +23,10 @@ public class AdoptionController { private final AdoptionService adoptionService; private final UserRepository userRepository; - private final CustomerRepository customerRepository; - public AdoptionController(AdoptionService adoptionService, UserRepository userRepository, CustomerRepository customerRepository) { + public AdoptionController(AdoptionService adoptionService, UserRepository userRepository) { this.adoptionService = adoptionService; this.userRepository = userRepository; - this.customerRepository = customerRepository; } @GetMapping @@ -45,8 +42,8 @@ public class AdoptionController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId)); @@ -63,8 +60,8 @@ public class AdoptionController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } return ResponseEntity.ok(adoptionService.getAdoptionById(id, customerId)); 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 35246e05..cf04f041 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -3,8 +3,7 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.appointment.AppointmentRequest; import com.petshop.backend.dto.appointment.AppointmentResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AppointmentService; import com.petshop.backend.util.AuthenticationHelper; @@ -27,12 +26,10 @@ public class AppointmentController { private final AppointmentService appointmentService; private final UserRepository userRepository; - private final CustomerRepository customerRepository; - public AppointmentController(AppointmentService appointmentService, UserRepository userRepository, CustomerRepository customerRepository) { + public AppointmentController(AppointmentService appointmentService, UserRepository userRepository) { this.appointmentService = appointmentService; this.userRepository = userRepository; - this.customerRepository = customerRepository; } @GetMapping @@ -48,8 +45,8 @@ public class AppointmentController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable, customerId)); @@ -66,8 +63,8 @@ public class AppointmentController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } return ResponseEntity.ok(appointmentService.getAppointmentById(id, customerId)); @@ -83,8 +80,8 @@ public class AppointmentController { .orElse(null); if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - if (!request.getCustomerId().equals(customer.getCustomerId())) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + if (!request.getCustomerId().equals(user.getId())) { throw new org.springframework.security.access.AccessDeniedException("You can only create appointments for yourself"); } } 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 106ea66f..641c7a6b 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -7,15 +7,11 @@ import com.petshop.backend.dto.auth.ProfileUpdateRequest; import com.petshop.backend.dto.auth.RegisterRequest; import com.petshop.backend.dto.auth.RegisterResponse; import com.petshop.backend.dto.auth.UserInfoResponse; -import com.petshop.backend.entity.EmployeeStore; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.AvatarStorageService; -import com.petshop.backend.service.UserBusinessLinkageService; import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.core.io.Resource; @@ -44,22 +40,14 @@ public class AuthController { private final UserRepository userRepository; private final JwtUtil jwtUtil; private final PasswordEncoder passwordEncoder; - private final UserBusinessLinkageService userBusinessLinkageService; - private final EmployeeRepository employeeRepository; - private final EmployeeStoreRepository employeeStoreRepository; private final AvatarStorageService avatarStorageService; - private final CustomerRepository customerRepository; - public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, AvatarStorageService avatarStorageService, CustomerRepository customerRepository) { + public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.jwtUtil = jwtUtil; this.passwordEncoder = passwordEncoder; - this.userBusinessLinkageService = userBusinessLinkageService; - this.employeeRepository = employeeRepository; - this.employeeStoreRepository = employeeStoreRepository; this.avatarStorageService = avatarStorageService; - this.customerRepository = customerRepository; } @PostMapping("/register") @@ -94,9 +82,6 @@ public class AuthController { User savedUser = userRepository.save(user); - // Create or link customer record - userBusinessLinkageService.ensureLinkedCustomer(savedUser); - String token = jwtUtil.generateToken(savedUser); return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse( @@ -148,22 +133,7 @@ public class AuthController { @GetMapping("/me") public ResponseEntity getCurrentUser() { User user = getAuthenticatedUser(); - - EmployeeStore employeeStore = resolveEmployeeStore(user); - Long customerId = resolveCustomerId(user); - - return ResponseEntity.ok(new UserInfoResponse( - user.getId(), - user.getUsername(), - user.getEmail(), - user.getFullName(), - user.getPhone(), - avatarStorageService.toOwnerAvatarUrl(user), - user.getRole().name(), - customerId, - employeeStore != null ? employeeStore.getStore().getStoreId() : null, - employeeStore != null ? employeeStore.getStore().getStoreName() : null - )); + return ResponseEntity.ok(toUserInfoResponse(user)); } @PutMapping("/me") @@ -218,39 +188,24 @@ public class AuthController { } User updatedUser = userRepository.save(user); - userBusinessLinkageService.syncLinkedRecords(updatedUser); + return ResponseEntity.ok(toUserInfoResponse(updatedUser)); + } - EmployeeStore employeeStore = resolveEmployeeStore(updatedUser); - Long customerId = resolveCustomerId(updatedUser); - - return ResponseEntity.ok(new UserInfoResponse( - updatedUser.getId(), - updatedUser.getUsername(), - updatedUser.getEmail(), - updatedUser.getFullName(), - updatedUser.getPhone(), - avatarStorageService.toOwnerAvatarUrl(updatedUser), - updatedUser.getRole().name(), + private UserInfoResponse toUserInfoResponse(User user) { + StoreLocation primaryStore = user.getPrimaryStore(); + Long customerId = user.getRole() == User.Role.CUSTOMER ? user.getId() : null; + return new UserInfoResponse( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getFullName(), + user.getPhone(), + avatarStorageService.toOwnerAvatarUrl(user), + user.getRole().name(), customerId, - employeeStore != null ? employeeStore.getStore().getStoreId() : null, - employeeStore != null ? employeeStore.getStore().getStoreName() : null - )); - } - - private EmployeeStore resolveEmployeeStore(User user) { - if (user.getRole() == User.Role.CUSTOMER) { - return null; - } - - return employeeRepository.findByUserId(user.getId()) - .flatMap(employee -> employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId())) - .orElse(null); - } - - private Long resolveCustomerId(User user) { - return customerRepository.findByUserId(user.getId()) - .map(c -> c.getCustomerId()) - .orElse(null); + primaryStore != null ? primaryStore.getStoreId() : null, + primaryStore != null ? primaryStore.getStoreName() : null + ); } private String trimToNull(String value) { 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 7320cdb9..e56e353f 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ChatController.java @@ -6,7 +6,6 @@ import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.dto.chat.UpdateConversationRequest; import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; @@ -27,13 +26,11 @@ public class ChatController { private final ChatService chatService; private final ChatRealtimeService chatRealtimeService; private final UserRepository userRepository; - private final CustomerRepository customerRepository; - public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, CustomerRepository customerRepository) { + public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository) { this.chatService = chatService; this.chatRealtimeService = chatRealtimeService; this.userRepository = userRepository; - this.customerRepository = customerRepository; } private User getCurrentUser() { 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 f3ab880e..4f17dd4f 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java +++ b/backend/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -1,9 +1,9 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; -import com.petshop.backend.dto.customer.CustomerRequest; -import com.petshop.backend.dto.customer.CustomerResponse; -import com.petshop.backend.service.CustomerService; +import com.petshop.backend.dto.user.UserRequest; +import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.service.UserService; import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -17,45 +17,45 @@ import org.springframework.web.bind.annotation.*; @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class CustomerController { - private final CustomerService customerService; + private final UserService userService; - public CustomerController(CustomerService customerService) { - this.customerService = customerService; + public CustomerController(UserService userService) { + this.userService = userService; } @GetMapping - public ResponseEntity> getAllCustomers( + public ResponseEntity> getAllCustomers( @RequestParam(required = false) String q, Pageable pageable) { - return ResponseEntity.ok(customerService.getAllCustomers(q, pageable)); + return ResponseEntity.ok(userService.getAllUsers(q, "CUSTOMER", pageable)); } @GetMapping("/{id}") - public ResponseEntity getCustomerById(@PathVariable Long id) { - return ResponseEntity.ok(customerService.getCustomerById(id)); + public ResponseEntity getCustomerById(@PathVariable Long id) { + return ResponseEntity.ok(userService.getUserById(id)); } @PostMapping - public ResponseEntity createCustomer(@Valid @RequestBody CustomerRequest request) { - return ResponseEntity.status(HttpStatus.CREATED).body(customerService.createCustomer(request)); + public ResponseEntity createCustomer(@Valid @RequestBody UserRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(request)); } @PutMapping("/{id}") - public ResponseEntity updateCustomer( + public ResponseEntity updateCustomer( @PathVariable Long id, - @Valid @RequestBody CustomerRequest request) { - return ResponseEntity.ok(customerService.updateCustomer(id, request)); + @Valid @RequestBody UserRequest request) { + return ResponseEntity.ok(userService.updateUser(id, request)); } @DeleteMapping("/{id}") public ResponseEntity deleteCustomer(@PathVariable Long id) { - customerService.deleteCustomer(id); + userService.deleteUser(id); return ResponseEntity.noContent().build(); } @PostMapping("/bulk-delete") public ResponseEntity bulkDeleteCustomers(@Valid @RequestBody BulkDeleteRequest request) { - customerService.bulkDeleteCustomers(request); + userService.bulkDeleteUsers(request); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/com/petshop/backend/controller/CustomerPetController.java b/backend/src/main/java/com/petshop/backend/controller/CustomerPetController.java deleted file mode 100644 index 4fd6648b..00000000 --- a/backend/src/main/java/com/petshop/backend/controller/CustomerPetController.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.petshop.backend.controller; - -import com.petshop.backend.dto.customerpet.CustomerPetRequest; -import com.petshop.backend.dto.customerpet.CustomerPetResponse; -import com.petshop.backend.service.CatalogImageStorageService; -import com.petshop.backend.service.CustomerPetService; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.util.AuthenticationHelper; -import jakarta.validation.Valid; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/v1/my-pets") -@PreAuthorize("hasRole('CUSTOMER')") -public class CustomerPetController { - - private final CustomerPetService customerPetService; - private final CustomerPetRepository customerPetRepository; - private final CustomerRepository customerRepository; - private final UserRepository userRepository; - private final CatalogImageStorageService catalogImageStorageService; - - public CustomerPetController(CustomerPetService customerPetService, - CustomerPetRepository customerPetRepository, - CustomerRepository customerRepository, - UserRepository userRepository, - CatalogImageStorageService catalogImageStorageService) { - this.customerPetService = customerPetService; - this.customerPetRepository = customerPetRepository; - this.customerRepository = customerRepository; - this.userRepository = userRepository; - this.catalogImageStorageService = catalogImageStorageService; - } - - @GetMapping - public ResponseEntity> getMyPets() { - - return ResponseEntity.ok(customerPetService.getMyPets()); - } - - @PostMapping - public ResponseEntity createPet(@Valid @RequestBody CustomerPetRequest request) { - - return ResponseEntity.status(HttpStatus.CREATED).body(customerPetService.createPet(request)); - } - - @PutMapping("/{id}") - public ResponseEntity updatePet(@PathVariable Long id, @Valid @RequestBody CustomerPetRequest request) { - - return ResponseEntity.ok(customerPetService.updatePet(id, request)); - } - - @DeleteMapping("/{id}") - public ResponseEntity deletePet(@PathVariable Long id) { - customerPetService.deletePet(id); - - return ResponseEntity.noContent().build(); - } - - @PostMapping("/{id}/image") - public ResponseEntity uploadImage(@PathVariable Long id, @RequestParam("image") MultipartFile image) { - try { - - return ResponseEntity.ok(customerPetService.uploadImage(id, image)); - } - - catch (IllegalArgumentException ex) { - Map error = new HashMap<>(); - error.put("message", ex.getMessage()); - - return ResponseEntity.badRequest().body(error); - } - - catch (IOException ex) { - Map error = new HashMap<>(); - error.put("message", "Failed to upload image: " + ex.getMessage()); - - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); - } - } - - @GetMapping("/{id}/image") - public ResponseEntity getImage(@PathVariable Long id) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElse(null); - - if (pet == null || pet.getImageUrl() == null || pet.getImageUrl().isBlank()) { - - return ResponseEntity.notFound().build(); - } - - Resource resource = catalogImageStorageService.loadPetImage(pet.getImageUrl()); - MediaType mediaType = catalogImageStorageService.resolveMediaType(resource); - - return ResponseEntity.ok().contentType(mediaType).body(resource); - } - - @DeleteMapping("/{id}/image") - public ResponseEntity deleteImage(@PathVariable Long id) { - - return ResponseEntity.ok(customerPetService.deleteImage(id)); - } -} 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 d0a69036..4d995cd4 100644 --- a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -1,8 +1,6 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.common.DropdownOption; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.entity.EmployeeStore; import com.petshop.backend.entity.User; import com.petshop.backend.repository.*; import org.springframework.http.ResponseEntity; @@ -20,31 +18,24 @@ import java.util.stream.Collectors; public class DropdownController { private final PetRepository petRepository; - private final CustomerRepository customerRepository; - private final CustomerPetRepository customerPetRepository; private final ServiceRepository serviceRepository; private final ProductRepository productRepository; private final CategoryRepository categoryRepository; private final StoreRepository storeRepository; private final SupplierRepository supplierRepository; - private final EmployeeStoreRepository employeeStoreRepository; private final UserRepository userRepository; - public DropdownController(PetRepository petRepository, CustomerRepository customerRepository, - CustomerPetRepository customerPetRepository, + public DropdownController(PetRepository petRepository, ServiceRepository serviceRepository, ProductRepository productRepository, CategoryRepository categoryRepository, StoreRepository storeRepository, - SupplierRepository supplierRepository, EmployeeStoreRepository employeeStoreRepository, + SupplierRepository supplierRepository, UserRepository userRepository) { this.petRepository = petRepository; - this.customerRepository = customerRepository; - this.customerPetRepository = customerPetRepository; this.serviceRepository = serviceRepository; this.productRepository = productRepository; this.categoryRepository = categoryRepository; this.storeRepository = storeRepository; this.supplierRepository = supplierRepository; - this.employeeStoreRepository = employeeStoreRepository; this.userRepository = userRepository; } @@ -71,8 +62,8 @@ public class DropdownController { @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getCustomers() { return ResponseEntity.ok( - customerRepository.findAll().stream() - .map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName())) + userRepository.findByRoleAndActiveTrue(User.Role.CUSTOMER).stream() + .map(u -> new DropdownOption(u.getId(), u.getFirstName() + " " + u.getLastName())) .collect(Collectors.toList()) ); } @@ -81,18 +72,8 @@ public class DropdownController { @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getAppointmentCustomers() { return ResponseEntity.ok( - customerRepository.findAllWithPets().stream() - .map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName())) - .collect(Collectors.toList()) - ); - } - - @GetMapping("/customers/{customerId}/pets") - @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") - public ResponseEntity> getCustomerPets(@PathVariable Long customerId) { - return ResponseEntity.ok( - customerPetRepository.findByCustomerCustomerIdOrderByPetNameAsc(customerId).stream() - .map(this::toCustomerPetOption) + userRepository.findByRoleAndActiveTrue(User.Role.CUSTOMER).stream() + .map(u -> new DropdownOption(u.getId(), u.getFirstName() + " " + u.getLastName())) .collect(Collectors.toList()) ); } @@ -159,17 +140,15 @@ public class DropdownController { @GetMapping({"/stores/{storeId}/employees", "/employees"}) @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity> getStoreEmployees(@PathVariable(required = false) Long storeId) { - List employees; + List employees; if (storeId == null || storeId == 0) { - employees = employeeStoreRepository.findActiveAllOrderByEmployeeEmployeeIdAsc(); + employees = userRepository.findByRoleAndActiveTrue(User.Role.STAFF); } else { - employees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId); + employees = userRepository.findByPrimaryStoreStoreIdAndRoleAndActiveTrue(storeId, User.Role.STAFF); } return ResponseEntity.ok( employees.stream() - .filter(this::isAssignableEmployee) - .map(this::toEmployeeOption) - .distinct() + .map(u -> new DropdownOption(u.getId(), u.getFirstName() + " " + u.getLastName())) .collect(Collectors.toList()) ); } @@ -183,26 +162,4 @@ public class DropdownController { .collect(Collectors.toList()) ); } - - private DropdownOption toCustomerPetOption(CustomerPet pet) { - String species = pet.getSpecies() == null || pet.getSpecies().isBlank() ? "Pet" : pet.getSpecies(); - String breed = pet.getBreed() == null || pet.getBreed().isBlank() ? "" : " · " + pet.getBreed(); - return new DropdownOption(pet.getCustomerPetId(), pet.getPetName() + " (" + species + breed + ")"); - } - - private DropdownOption toEmployeeOption(EmployeeStore employeeStore) { - var employee = employeeStore.getEmployee(); - return new DropdownOption(employee.getEmployeeId(), employee.getFirstName() + " " + employee.getLastName()); - } - - private boolean isAssignableEmployee(EmployeeStore employeeStore) { - Long userId = employeeStore.getEmployee().getUserId(); - if (userId == null) { - return false; - } - return userRepository.findById(userId) - .filter(user -> user.getRole() == User.Role.STAFF) - .filter(user -> Boolean.TRUE.equals(user.getActive())) - .isPresent(); - } } 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 1c567623..2276e6ad 100644 --- a/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java +++ b/backend/src/main/java/com/petshop/backend/controller/EmployeeController.java @@ -1,8 +1,8 @@ package com.petshop.backend.controller; -import com.petshop.backend.dto.employee.EmployeeRequest; -import com.petshop.backend.dto.employee.EmployeeResponse; -import com.petshop.backend.service.EmployeeService; +import com.petshop.backend.dto.user.UserRequest; +import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.service.UserService; import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,35 +15,40 @@ import org.springframework.web.bind.annotation.*; @RequestMapping("/api/v1/employees") @PreAuthorize("hasRole('ADMIN')") public class EmployeeController { - private final EmployeeService employeeService; - public EmployeeController(EmployeeService employeeService) { - this.employeeService = employeeService; + private final UserService userService; + + public EmployeeController(UserService userService) { + this.userService = userService; } @GetMapping - public ResponseEntity> getAllEmployees(@RequestParam(required = false) String q, Pageable pageable) { - return ResponseEntity.ok(employeeService.getAllEmployees(q, pageable)); + public ResponseEntity> getAllEmployees( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(userService.getAllUsers(q, "STAFF", pageable)); } @GetMapping("/{id}") - public ResponseEntity getEmployeeById(@PathVariable Long id) { - return ResponseEntity.ok(employeeService.getEmployeeById(id)); + public ResponseEntity getEmployeeById(@PathVariable Long id) { + return ResponseEntity.ok(userService.getUserById(id)); } @PostMapping - public ResponseEntity createEmployee(@Valid @RequestBody EmployeeRequest request) { - return ResponseEntity.status(HttpStatus.CREATED).body(employeeService.createEmployee(request)); + public ResponseEntity createEmployee(@Valid @RequestBody UserRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(request)); } @PutMapping("/{id}") - public ResponseEntity updateEmployee(@PathVariable Long id, @Valid @RequestBody EmployeeRequest request) { - return ResponseEntity.ok(employeeService.updateEmployee(id, request)); + public ResponseEntity updateEmployee( + @PathVariable Long id, + @Valid @RequestBody UserRequest request) { + return ResponseEntity.ok(userService.updateUser(id, request)); } @DeleteMapping("/{id}") public ResponseEntity deleteEmployee(@PathVariable Long id) { - employeeService.deleteEmployee(id); + userService.deleteUser(id); return ResponseEntity.noContent().build(); } } 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 6968b9c3..bd6f158b 100644 --- a/backend/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/backend/src/main/java/com/petshop/backend/controller/RefundController.java @@ -3,8 +3,7 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.refund.RefundRequest; import com.petshop.backend.dto.refund.RefundResponse; import com.petshop.backend.dto.refund.RefundUpdateRequest; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.RefundService; import com.petshop.backend.util.AuthenticationHelper; @@ -26,12 +25,10 @@ public class RefundController { private final RefundService refundService; private final UserRepository userRepository; - private final CustomerRepository customerRepository; - public RefundController(RefundService refundService, UserRepository userRepository, CustomerRepository customerRepository) { + public RefundController(RefundService refundService, UserRepository userRepository) { this.refundService = refundService; this.userRepository = userRepository; - this.customerRepository = customerRepository; } @PostMapping @@ -46,8 +43,8 @@ public class RefundController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } RefundResponse refund = refundService.createRefund(request, customerId); @@ -70,8 +67,8 @@ public class RefundController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } List refunds = refundService.getAllRefunds(customerId); @@ -90,8 +87,8 @@ public class RefundController { Long customerId = null; if (role != null && role.equals("CUSTOMER")) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - customerId = customer.getCustomerId(); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } RefundResponse refund = refundService.getRefundById(id, customerId); 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 3d127c19..c60c3fcd 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 @@ -27,8 +27,6 @@ public class AppointmentRequest { private List petIds; - private List customerPetIds; - private Long employeeId; public Long getCustomerId() { @@ -87,14 +85,6 @@ public class AppointmentRequest { this.petIds = petIds; } - public List getCustomerPetIds() { - return customerPetIds; - } - - public void setCustomerPetIds(List customerPetIds) { - this.customerPetIds = customerPetIds; - } - public Long getEmployeeId() { return employeeId; } @@ -115,13 +105,12 @@ public class AppointmentRequest { Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petIds, that.petIds) && - Objects.equals(customerPetIds, that.customerPetIds) && Objects.equals(employeeId, that.employeeId); } @Override public int hashCode() { - return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, customerPetIds, employeeId); + return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, employeeId); } @Override @@ -134,7 +123,6 @@ public class AppointmentRequest { ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + ", petIds=" + petIds + - ", customerPetIds=" + customerPetIds + ", employeeId=" + employeeId + '}'; } 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 efc1c300..f8e14ac2 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 @@ -21,8 +21,6 @@ public class AppointmentResponse { private String employeeName; private List petNames; private List petIds; - private List customerPetNames; - private List customerPetIds; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -158,24 +156,6 @@ public class AppointmentResponse { this.petIds = petIds; } - public List getCustomerPetNames() { - - return customerPetNames; - } - - public void setCustomerPetNames(List customerPetNames) { - this.customerPetNames = customerPetNames; - } - - public List getCustomerPetIds() { - - return customerPetIds; - } - - public void setCustomerPetIds(List customerPetIds) { - this.customerPetIds = customerPetIds; - } - public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java deleted file mode 100644 index ded898e3..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.petshop.backend.dto.customer; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import java.util.Objects; - -public class CustomerRequest { - @NotBlank(message = "First name is required") - private String firstName; - - @NotBlank(message = "Last name is required") - private String lastName; - - @Email(message = "Invalid email format") - private String email; - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CustomerRequest that = (CustomerRequest) o; - return Objects.equals(firstName, that.firstName) && - Objects.equals(lastName, that.lastName) && - Objects.equals(email, that.email); - } - - @Override - public int hashCode() { - return Objects.hash(firstName, lastName, email); - } - - @Override - public String toString() { - return "CustomerRequest{" + - "firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - '}'; - } -} diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java deleted file mode 100644 index bd05bf76..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.petshop.backend.dto.customer; - -import java.time.LocalDateTime; -import java.util.Objects; - -public class CustomerResponse { - private Long customerId; - private String firstName; - private String lastName; - private String email; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public CustomerResponse() { - } - - public CustomerResponse(Long customerId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.customerId = customerId; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public Long getCustomerId() { - return customerId; - } - - public void setCustomerId(Long customerId) { - this.customerId = customerId; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CustomerResponse that = (CustomerResponse) o; - return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); - } - - @Override - public int hashCode() { - return Objects.hash(customerId, firstName, lastName, email, createdAt, updatedAt); - } - - @Override - public String toString() { - return "CustomerResponse{" + - "customerId=" + customerId + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } -} diff --git a/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetRequest.java b/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetRequest.java deleted file mode 100644 index b4b37355..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.petshop.backend.dto.customerpet; - -import jakarta.validation.constraints.NotBlank; - -import java.util.Objects; - -public class CustomerPetRequest { - - @NotBlank(message = "Pet name is required") - private String petName; - - @NotBlank(message = "Species is required") - private String species; - - private String breed; - - public String getPetName() { - - return petName; - } - - public void setPetName(String petName) { - this.petName = petName; - } - - public String getSpecies() { - - return species; - } - - public void setSpecies(String species) { - this.species = species; - } - - public String getBreed() { - - return breed; - } - - public void setBreed(String breed) { - this.breed = breed; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - - return true; - } - - if (o == null || getClass() != o.getClass()) { - - return false; - } - - CustomerPetRequest that = (CustomerPetRequest) o; - - return Objects.equals(petName, that.petName) && Objects.equals(species, that.species) && Objects.equals(breed, that.breed); - } - - @Override - public int hashCode() { - - return Objects.hash(petName, species, breed); - } -} diff --git a/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetResponse.java b/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetResponse.java deleted file mode 100644 index 8e9ab37d..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/customerpet/CustomerPetResponse.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.petshop.backend.dto.customerpet; - -import java.time.LocalDateTime; -import java.util.Objects; - -public class CustomerPetResponse { - - private Long customerPetId; - private Long customerId; - private String petName; - private String species; - private String breed; - private String imageUrl; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public CustomerPetResponse() { - } - - public CustomerPetResponse(Long customerPetId, Long customerId, String petName, String species, String breed, String imageUrl, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.customerPetId = customerPetId; - this.customerId = customerId; - this.petName = petName; - this.species = species; - this.breed = breed; - this.imageUrl = imageUrl; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public Long getCustomerPetId() { - - return customerPetId; - } - - public void setCustomerPetId(Long customerPetId) { - this.customerPetId = customerPetId; - } - - public Long getCustomerId() { - - return customerId; - } - - public void setCustomerId(Long customerId) { - this.customerId = customerId; - } - - public String getPetName() { - - return petName; - } - - public void setPetName(String petName) { - this.petName = petName; - } - - public String getSpecies() { - - return species; - } - - public void setSpecies(String species) { - this.species = species; - } - - public String getBreed() { - - return breed; - } - - public void setBreed(String breed) { - this.breed = breed; - } - - public String getImageUrl() { - - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public LocalDateTime getCreatedAt() { - - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - CustomerPetResponse that = (CustomerPetResponse) o; - - return Objects.equals(customerPetId, that.customerPetId); - } - - @Override - public int hashCode() { - - return Objects.hash(customerPetId); - } -} diff --git a/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java b/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java deleted file mode 100644 index f5fb9020..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.petshop.backend.dto.employee; - -import com.petshop.backend.entity.User; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -public class EmployeeRequest { - @NotBlank(message = "Username is required") - @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") - private String username; - - @Size(min = 6, message = "Password must be at least 6 characters") - private String password; - - @NotBlank(message = "First name is required") - private String firstName; - - @NotBlank(message = "Last name is required") - private String lastName; - - @Email(message = "Invalid email format") - private String email; - - @NotBlank(message = "Phone is required") - @Size(max = 20, message = "Phone must not exceed 20 characters") - private String phone; - - @NotNull(message = "Role is required") - private User.Role role; - - private Boolean active = true; - - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } - public String getFirstName() { return firstName; } - public void setFirstName(String firstName) { this.firstName = firstName; } - public String getLastName() { return lastName; } - public void setLastName(String lastName) { this.lastName = lastName; } - public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } - public String getPhone() { return phone; } - public void setPhone(String phone) { this.phone = phone; } - public User.Role getRole() { return role; } - public void setRole(User.Role role) { this.role = role; } - public Boolean getActive() { return active; } - public void setActive(Boolean active) { this.active = active; } -} diff --git a/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java b/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java deleted file mode 100644 index a159fc35..00000000 --- a/backend/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.petshop.backend.dto.employee; - -import java.time.LocalDateTime; - -public class EmployeeResponse { - private Long employeeId; - private Long userId; - private String username; - private String firstName; - private String lastName; - private String fullName; - private String email; - private String phone; - private String role; - private Boolean active; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public Long getEmployeeId() { return employeeId; } - public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; } - public Long getUserId() { return userId; } - public void setUserId(Long userId) { this.userId = userId; } - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getFirstName() { return firstName; } - public void setFirstName(String firstName) { this.firstName = firstName; } - public String getLastName() { return lastName; } - public void setLastName(String lastName) { this.lastName = lastName; } - public String getFullName() { return fullName; } - public void setFullName(String fullName) { this.fullName = fullName; } - public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } - public String getPhone() { return phone; } - public void setPhone(String phone) { this.phone = phone; } - public String getRole() { return role; } - public void setRole(String role) { this.role = role; } - public Boolean getActive() { return active; } - public void setActive(Boolean active) { this.active = active; } - public LocalDateTime getCreatedAt() { return createdAt; } - public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } -} 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 211f75de..7c778bc3 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java +++ b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java @@ -14,8 +14,8 @@ public class ActivityLog { private Long logId; @ManyToOne - @JoinColumn(name = "employeeId", nullable = false) - private Employee employee; + @JoinColumn(name = "userId", nullable = false) + private User user; @Column(nullable = false, columnDefinition = "TEXT") private String activity; @@ -26,9 +26,9 @@ public class ActivityLog { public ActivityLog() { } - public ActivityLog(Long logId, Employee employee, String activity, LocalDateTime logTimestamp) { + public ActivityLog(Long logId, User user, String activity, LocalDateTime logTimestamp) { this.logId = logId; - this.employee = employee; + this.user = user; this.activity = activity; this.logTimestamp = logTimestamp; } @@ -41,12 +41,12 @@ public class ActivityLog { this.logId = logId; } - public Employee getEmployee() { - return employee; + public User getUser() { + return user; } - public void setEmployee(Employee employee) { - this.employee = employee; + public void setUser(User user) { + this.user = user; } public String getActivity() { @@ -82,7 +82,7 @@ public class ActivityLog { public String toString() { return "ActivityLog{" + "logId=" + logId + - ", employee=" + employee + + ", user=" + user + ", activity='" + activity + '\'' + ", logTimestamp=" + logTimestamp + '}'; 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 78360e2e..35d4edac 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/backend/src/main/java/com/petshop/backend/entity/Adoption.java @@ -4,7 +4,6 @@ import jakarta.persistence.*; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; -import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; @@ -23,11 +22,11 @@ public class Adoption { @ManyToOne @JoinColumn(name = "customerId", nullable = false) - private Customer customer; + private User customer; @ManyToOne @JoinColumn(name = "employeeId", nullable = false) - private Employee employee; + private User employee; @Column(nullable = false) private LocalDate adoptionDate; @@ -46,17 +45,6 @@ public class Adoption { public Adoption() { } - public Adoption(Long adoptionId, Pet pet, Customer customer, Employee employee, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.adoptionId = adoptionId; - this.pet = pet; - this.customer = customer; - this.employee = employee; - this.adoptionDate = adoptionDate; - this.adoptionStatus = adoptionStatus; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getAdoptionId() { return adoptionId; } @@ -73,19 +61,19 @@ public class Adoption { this.pet = pet; } - public Customer getCustomer() { + public User getCustomer() { return customer; } - public void setCustomer(Customer customer) { + public void setCustomer(User customer) { this.customer = customer; } - public Employee getEmployee() { + public User getEmployee() { return employee; } - public void setEmployee(Employee employee) { + public void setEmployee(User employee) { this.employee = employee; } @@ -138,13 +126,8 @@ public class Adoption { public String toString() { return "Adoption{" + "adoptionId=" + adoptionId + - ", pet=" + pet + - ", customer=" + customer + - ", employee=" + employee + - ", adoptionDate=" + adoptionDate + ", adoptionStatus='" + adoptionStatus + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + + ", adoptionDate=" + adoptionDate + '}'; } } 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 d4ebc199..f313d928 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/backend/src/main/java/com/petshop/backend/entity/Appointment.java @@ -21,7 +21,7 @@ public class Appointment { @ManyToOne @JoinColumn(name = "customerId", nullable = false) - private Customer customer; + private User customer; @ManyToOne @JoinColumn(name = "storeId", nullable = false) @@ -33,7 +33,7 @@ public class Appointment { @ManyToOne @JoinColumn(name = "employeeId", nullable = false) - private Employee employee; + private User employee; @Column(nullable = false) private LocalDate appointmentDate; @@ -52,14 +52,6 @@ public class Appointment { ) private Set pets = new HashSet<>(); - @ManyToMany - @JoinTable( - name = "appointment_customer_pet", - joinColumns = @JoinColumn(name = "appointment_id"), - inverseJoinColumns = @JoinColumn(name = "customer_pet_id") - ) - private Set customerPets = new HashSet<>(); - @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -71,20 +63,6 @@ public class Appointment { public Appointment() { } - public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, Employee employee, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.appointmentId = appointmentId; - this.customer = customer; - this.store = store; - this.service = service; - this.employee = employee; - this.appointmentDate = appointmentDate; - this.appointmentTime = appointmentTime; - this.appointmentStatus = appointmentStatus; - this.pets = pets; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getAppointmentId() { return appointmentId; } @@ -93,11 +71,11 @@ public class Appointment { this.appointmentId = appointmentId; } - public Customer getCustomer() { + public User getCustomer() { return customer; } - public void setCustomer(Customer customer) { + public void setCustomer(User customer) { this.customer = customer; } @@ -117,11 +95,11 @@ public class Appointment { this.service = service; } - public Employee getEmployee() { + public User getEmployee() { return employee; } - public void setEmployee(Employee employee) { + public void setEmployee(User employee) { this.employee = employee; } @@ -157,15 +135,6 @@ public class Appointment { this.pets = pets; } - public Set getCustomerPets() { - - return customerPets; - } - - public void setCustomerPets(Set customerPets) { - this.customerPets = customerPets; - } - public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/backend/src/main/java/com/petshop/backend/entity/Customer.java b/backend/src/main/java/com/petshop/backend/entity/Customer.java deleted file mode 100644 index 09035619..00000000 --- a/backend/src/main/java/com/petshop/backend/entity/Customer.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; - -import java.time.LocalDateTime; -import java.util.Objects; - -@Entity -@Table(name = "customer") -public class Customer { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long customerId; - - @Column(name = "user_id") - private Long userId; - - @Column(nullable = false, length = 50) - private String firstName; - - @Column(nullable = false, length = 50) - private String lastName; - - @Column(nullable = false, length = 100) - private String email; - - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - @UpdateTimestamp - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - public Customer() { - } - - public Customer(Long customerId, Long userId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.customerId = customerId; - this.userId = userId; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public Long getCustomerId() { - return customerId; - } - - public void setCustomerId(Long customerId) { - this.customerId = customerId; - } - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Customer customer = (Customer) o; - return Objects.equals(customerId, customer.customerId); - } - - @Override - public int hashCode() { - return Objects.hash(customerId); - } - - @Override - public String toString() { - return "Customer{" + - "customerId=" + customerId + - ", userId=" + userId + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } -} diff --git a/backend/src/main/java/com/petshop/backend/entity/CustomerPet.java b/backend/src/main/java/com/petshop/backend/entity/CustomerPet.java deleted file mode 100644 index df75df8c..00000000 --- a/backend/src/main/java/com/petshop/backend/entity/CustomerPet.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; - -import java.time.LocalDateTime; -import java.util.Objects; - -@Entity -@Table(name = "customer_pet") -public class CustomerPet { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "customer_pet_id") - private Long customerPetId; - - @ManyToOne - @JoinColumn(name = "customer_id", nullable = false) - private Customer customer; - - @Column(name = "pet_name", nullable = false, length = 50) - private String petName; - - @Column(nullable = false, length = 50) - private String species; - - @Column(length = 50) - private String breed; - - @Column(name = "image_url", length = 255) - private String imageUrl; - - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - @UpdateTimestamp - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - public CustomerPet() { - } - - public Long getCustomerPetId() { - - return customerPetId; - } - - public void setCustomerPetId(Long customerPetId) { - this.customerPetId = customerPetId; - } - - public Customer getCustomer() { - - return customer; - } - - public void setCustomer(Customer customer) { - this.customer = customer; - } - - public String getPetName() { - - return petName; - } - - public void setPetName(String petName) { - this.petName = petName; - } - - public String getSpecies() { - - return species; - } - - public void setSpecies(String species) { - this.species = species; - } - - public String getBreed() { - - return breed; - } - - public void setBreed(String breed) { - this.breed = breed; - } - - public String getImageUrl() { - - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public LocalDateTime getCreatedAt() { - - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - CustomerPet that = (CustomerPet) o; - return Objects.equals(customerPetId, that.customerPetId); - } - - @Override - public int hashCode() { - - return Objects.hash(customerPetId); - } -} diff --git a/backend/src/main/java/com/petshop/backend/entity/Employee.java b/backend/src/main/java/com/petshop/backend/entity/Employee.java deleted file mode 100644 index c88216f6..00000000 --- a/backend/src/main/java/com/petshop/backend/entity/Employee.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; - -import java.time.LocalDateTime; -import java.util.Objects; - -@Entity -@Table(name = "employee") -public class Employee { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long employeeId; - - @Column(name = "user_id") - private Long userId; - - @Column(nullable = false, length = 50) - private String firstName; - - @Column(nullable = false, length = 50) - private String lastName; - - @Column(nullable = false, length = 100) - private String email; - - @Column(nullable = false, length = 50) - private String role; - - @Column(nullable = false) - private Boolean isActive = true; - - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - @UpdateTimestamp - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - public Employee() { - } - - public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.employeeId = employeeId; - this.userId = userId; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - this.role = role; - this.isActive = isActive; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public Long getEmployeeId() { - return employeeId; - } - - public void setEmployeeId(Long employeeId) { - this.employeeId = employeeId; - } - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getRole() { - return role; - } - - public void setRole(String role) { - this.role = role; - } - - public Boolean getIsActive() { - return isActive; - } - - public void setIsActive(Boolean isActive) { - this.isActive = isActive; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Employee employee = (Employee) o; - return Objects.equals(employeeId, employee.employeeId); - } - - @Override - public int hashCode() { - return Objects.hash(employeeId); - } - - @Override - public String toString() { - return "Employee{" + - "employeeId=" + employeeId + - ", userId=" + userId + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", role='" + role + '\'' + - ", isActive=" + isActive + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } -} diff --git a/backend/src/main/java/com/petshop/backend/entity/EmployeeStore.java b/backend/src/main/java/com/petshop/backend/entity/EmployeeStore.java deleted file mode 100644 index daa2a2e2..00000000 --- a/backend/src/main/java/com/petshop/backend/entity/EmployeeStore.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; - -import java.io.Serializable; -import java.util.Objects; - -@Entity -@Table(name = "employeeStore") -@IdClass(EmployeeStore.EmployeeStoreId.class) -public class EmployeeStore { - - @Id - @ManyToOne - @JoinColumn(name = "employeeId", nullable = false) - private Employee employee; - - @Id - @ManyToOne - @JoinColumn(name = "storeId", nullable = false) - private StoreLocation store; - - public EmployeeStore() { - } - - public EmployeeStore(Employee employee, StoreLocation store) { - this.employee = employee; - this.store = store; - } - - public Employee getEmployee() { - return employee; - } - - public void setEmployee(Employee employee) { - this.employee = employee; - } - - public StoreLocation getStore() { - return store; - } - - public void setStore(StoreLocation store) { - this.store = store; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EmployeeStore that = (EmployeeStore) o; - return Objects.equals(employee, that.employee) && Objects.equals(store, that.store); - } - - @Override - public int hashCode() { - return Objects.hash(employee, store); - } - - @Override - public String toString() { - return "EmployeeStore{" + - "employee=" + employee + - ", store=" + store + - '}'; - } - - public static class EmployeeStoreId implements Serializable { - private Long employee; - private Long store; - - public EmployeeStoreId() { - } - - public EmployeeStoreId(Long employee, Long store) { - this.employee = employee; - this.store = store; - } - - public Long getEmployee() { - return employee; - } - - public void setEmployee(Long employee) { - this.employee = employee; - } - - public Long getStore() { - return store; - } - - public void setStore(Long store) { - this.store = store; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EmployeeStoreId that = (EmployeeStoreId) o; - return Objects.equals(employee, that.employee) && Objects.equals(store, that.store); - } - - @Override - public int hashCode() { - return Objects.hash(employee, store); - } - - @Override - public String toString() { - return "EmployeeStoreId{" + - "employee=" + employee + - ", store=" + store + - '}'; - } - } -} 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 d0b3b3fc..604a24db 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Pet.java +++ b/backend/src/main/java/com/petshop/backend/entity/Pet.java @@ -23,24 +23,24 @@ public class Pet { @Column(nullable = false, length = 50) private String petSpecies; - @Column(nullable = false, length = 50) + @Column(length = 50) private String petBreed; - @Column(nullable = false) + @Column private Integer petAge; @Column(nullable = false, length = 20) private String petStatus; - @Column(nullable = false, precision = 10, scale = 2) + @Column(precision = 10, scale = 2) private BigDecimal petPrice; @Column(length = 255) private String imageUrl; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "customerId") - private Customer customer; + @JoinColumn(name = "ownerUserId") + private User owner; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "storeId") @@ -57,19 +57,6 @@ public class Pet { public Pet() { } - public Pet(Long id, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, String imageUrl, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.petName = petName; - this.petSpecies = petSpecies; - this.petBreed = petBreed; - this.petAge = petAge; - this.petStatus = petStatus; - this.petPrice = petPrice; - this.imageUrl = imageUrl; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getPetId() { return id; } @@ -134,6 +121,22 @@ public class Pet { this.imageUrl = imageUrl; } + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public LocalDateTime getCreatedAt() { return createdAt; } @@ -150,22 +153,6 @@ public class Pet { this.updatedAt = updatedAt; } - public Customer getCustomer() { - return customer; - } - - public void setCustomer(Customer customer) { - this.customer = customer; - } - - public StoreLocation getStore() { - return store; - } - - public void setStore(StoreLocation store) { - this.store = store; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -185,13 +172,7 @@ public class Pet { "id=" + id + ", petName='" + petName + '\'' + ", petSpecies='" + petSpecies + '\'' + - ", petBreed='" + petBreed + '\'' + - ", petAge=" + petAge + ", petStatus='" + petStatus + '\'' + - ", petPrice=" + petPrice + - ", imageUrl='" + imageUrl + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + '}'; } } 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 c60c0927..ee1a9d51 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -23,7 +23,7 @@ public class Sale { @ManyToOne @JoinColumn(name = "employeeId", nullable = false) - private Employee employee; + private User employee; @ManyToOne @JoinColumn(name = "storeId", nullable = false) @@ -31,7 +31,7 @@ public class Sale { @ManyToOne @JoinColumn(name = "customerId") - private Customer customer; + private User customer; @Column(nullable = false, precision = 10, scale = 2) private BigDecimal totalAmount; @@ -60,21 +60,6 @@ public class Sale { public Sale() { } - public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, Customer customer, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.saleId = saleId; - this.saleDate = saleDate; - this.employee = employee; - this.store = store; - this.customer = customer; - this.totalAmount = totalAmount; - this.paymentMethod = paymentMethod; - this.isRefund = isRefund; - this.originalSale = originalSale; - this.items = items; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - public Long getSaleId() { return saleId; } @@ -91,11 +76,11 @@ public class Sale { this.saleDate = saleDate; } - public Employee getEmployee() { + public User getEmployee() { return employee; } - public void setEmployee(Employee employee) { + public void setEmployee(User employee) { this.employee = employee; } @@ -107,11 +92,11 @@ public class Sale { this.store = store; } - public Customer getCustomer() { + public User getCustomer() { return customer; } - public void setCustomer(Customer customer) { + public void setCustomer(User customer) { this.customer = customer; } @@ -189,16 +174,9 @@ public class Sale { return "Sale{" + "saleId=" + saleId + ", saleDate=" + saleDate + - ", employee=" + employee + - ", store=" + store + - ", customer=" + customer + ", totalAmount=" + totalAmount + ", paymentMethod='" + paymentMethod + '\'' + ", isRefund=" + isRefund + - ", originalSale=" + originalSale + - ", items=" + items + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + '}'; } } 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 7b632f7f..7502ec33 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -19,9 +19,9 @@ public interface AdoptionRepository extends JpaRepository { "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAdoptions(@Param("q") String query, Pageable pageable); - Page findByCustomerCustomerId(Long customerId, Pageable pageable); + Page findByCustomerId(Long customerId, Pageable pageable); - @Query("SELECT a FROM Adoption a WHERE a.customer.customerId = :customerId AND (" + + @Query("SELECT a FROM Adoption a WHERE a.customer.id = :customerId AND (" + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%')))") 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 f8649671..f4cdc564 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -28,18 +28,18 @@ public interface AppointmentRepository extends JpaRepository "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAppointments(@Param("q") String query, Pageable pageable); - Page findByCustomerCustomerId(Long customerId, Pageable pageable); + Page findByCustomerId(Long customerId, Pageable pageable); - @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE a.customer.customerId = :customerId AND (" + + @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE a.customer.id = :customerId AND (" + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')))") Page searchAppointmentsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable); - @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId = :employeeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") - List findByEmployeeEmployeeIdAndAppointmentDate(@Param("employeeId") Long employeeId, @Param("date") LocalDate date); + @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.id = :employeeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") + List findByEmployeeIdAndAppointmentDate(@Param("employeeId") Long employeeId, @Param("date") LocalDate date); - @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId IN :employeeIds AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") - List findByEmployeeEmployeeIdInAndAppointmentDate(@Param("employeeIds") List employeeIds, @Param("date") LocalDate date); + @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); } diff --git a/backend/src/main/java/com/petshop/backend/repository/CustomerPetRepository.java b/backend/src/main/java/com/petshop/backend/repository/CustomerPetRepository.java deleted file mode 100644 index 4fe0ef81..00000000 --- a/backend/src/main/java/com/petshop/backend/repository/CustomerPetRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.petshop.backend.repository; - -import com.petshop.backend.entity.CustomerPet; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface CustomerPetRepository extends JpaRepository { - - List findByCustomerCustomerIdOrderByCreatedAtDesc(Long customerId); - - List findByCustomerCustomerIdOrderByPetNameAsc(Long customerId); - - Optional findByCustomerPetIdAndCustomerCustomerId(Long customerPetId, Long customerId); -} diff --git a/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java deleted file mode 100644 index 2c860de7..00000000 --- a/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.petshop.backend.repository; - -import com.petshop.backend.entity.Customer; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface CustomerRepository extends JpaRepository { - - Optional findByUserId(Long userId); - List findAllByEmail(String email); - - @Query("SELECT DISTINCT c FROM Customer c WHERE EXISTS (SELECT cp FROM CustomerPet cp WHERE cp.customer = c) ORDER BY c.firstName ASC, c.lastName ASC") - List findAllWithPets(); - - @Query("SELECT c FROM Customer c WHERE " + - "LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "EXISTS (SELECT u FROM User u WHERE u.id = c.userId AND LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))") - Page searchCustomers(@Param("q") String query, Pageable pageable); -} diff --git a/backend/src/main/java/com/petshop/backend/repository/EmployeeRepository.java b/backend/src/main/java/com/petshop/backend/repository/EmployeeRepository.java deleted file mode 100644 index e320fc00..00000000 --- a/backend/src/main/java/com/petshop/backend/repository/EmployeeRepository.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.petshop.backend.repository; - -import com.petshop.backend.entity.Employee; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface EmployeeRepository extends JpaRepository { - Optional findByUserId(Long userId); - List findAllByEmail(String email); - Optional findFirstByIsActiveTrueOrderByEmployeeIdAsc(); - List findAllByIsActiveTrueOrderByEmployeeIdAsc(); - - @Query("SELECT e FROM Employee e WHERE " + - "LOWER(e.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(e.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(e.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(e.role) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "EXISTS (SELECT u FROM User u WHERE u.id = e.userId AND (" + - "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))))") - Page searchEmployees(@Param("q") String query, Pageable pageable); -} diff --git a/backend/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java b/backend/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java deleted file mode 100644 index 16a59cea..00000000 --- a/backend/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.petshop.backend.repository; - -import com.petshop.backend.entity.EmployeeStore; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface EmployeeStoreRepository extends JpaRepository { - Optional findByEmployeeEmployeeId(Long employeeId); - - @Query("SELECT es FROM EmployeeStore es WHERE es.store.storeId = :storeId AND es.employee.isActive = true ORDER BY es.employee.employeeId ASC") - List findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(@Param("storeId") Long storeId); - - @Query("SELECT es FROM EmployeeStore es WHERE es.employee.isActive = true ORDER BY es.employee.employeeId ASC") - List findActiveAllOrderByEmployeeEmployeeIdAsc(); -} 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 d01c2d85..fd55fabe 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java @@ -16,21 +16,21 @@ public interface PetRepository extends JpaRepository { List findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus); @Query("SELECT p FROM Pet p WHERE " + - "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + + "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + "(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " + "(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " + "(:storeId IS NULL OR p.store.storeId = :storeId)") Page searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, @Param("storeId") Long storeId, Pageable pageable); @Query("SELECT p FROM Pet p WHERE LOWER(p.petStatus) = 'available' AND " + - "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + + "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + "(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " + "(:storeId IS NULL OR p.store.storeId = :storeId)") Page searchPublicPets(@Param("q") String query, @Param("species") String species, @Param("storeId") Long storeId, Pageable pageable); @Query("SELECT DISTINCT p FROM Pet p LEFT JOIN Adoption a ON a.pet = p AND LOWER(a.adoptionStatus) = 'completed' WHERE " + - "(LOWER(p.petStatus) = 'available' OR a.customer.userId = :userId OR (LOWER(p.petStatus) = 'owned' AND p.customer.userId = :userId)) AND " + - "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + + "(LOWER(p.petStatus) = 'available' OR a.customer.id = :userId OR (LOWER(p.petStatus) = 'owned' AND p.owner.id = :userId)) AND " + + "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + "(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " + "(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))") Page searchCustomerVisiblePets(@Param("userId") Long userId, @Param("q") String query, @Param("species") String species, @Param("status") String status, Pageable pageable); 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 6bec352f..592a4a76 100644 --- a/backend/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -8,6 +8,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -17,16 +18,24 @@ public interface UserRepository extends JpaRepository { Optional findByPhone(String phone); boolean existsByUsername(String username); Page findByRole(User.Role role, Pageable pageable); + List findByRoleAndActiveTrue(User.Role role); + List findByPrimaryStoreStoreIdAndRoleAndActiveTrue(Long storeId, User.Role role); + Optional findFirstByPrimaryStoreStoreIdAndRoleAndActiveTrueOrderByIdAsc(Long storeId, User.Role role); + Optional findFirstByRoleAndActiveTrueOrderByIdAsc(User.Role role); @Query("SELECT u FROM User u WHERE " + - "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.username, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.firstName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.lastName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchUsers(@Param("q") String query, Pageable pageable); @Query("SELECT u FROM User u WHERE u.role = :role AND (" + - "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.username, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.firstName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.lastName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))") 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 c6ff1c60..caadfbb9 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -4,14 +4,10 @@ import com.petshop.backend.dto.adoption.AdoptionRequest; import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Employee; import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.data.domain.Page; @@ -30,15 +26,11 @@ public class AdoptionService { private final AdoptionRepository adoptionRepository; private final PetRepository petRepository; - private final CustomerRepository customerRepository; - private final EmployeeRepository employeeRepository; private final UserRepository userRepository; - public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, CustomerRepository customerRepository, EmployeeRepository employeeRepository, UserRepository userRepository) { + public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository) { this.adoptionRepository = adoptionRepository; this.petRepository = petRepository; - this.customerRepository = customerRepository; - this.employeeRepository = employeeRepository; this.userRepository = userRepository; } @@ -49,7 +41,7 @@ public class AdoptionService { if (query != null && !query.trim().isEmpty()) { adoptions = adoptionRepository.searchAdoptionsByCustomer(customerId, query, pageable); } else { - adoptions = adoptionRepository.findByCustomerCustomerId(customerId, pageable); + adoptions = adoptionRepository.findByCustomerId(customerId, pageable); } } else { if (query != null && !query.trim().isEmpty()) { @@ -66,7 +58,7 @@ public class AdoptionService { Adoption adoption = adoptionRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id)); - if (customerId != null && !adoption.getCustomer().getCustomerId().equals(customerId)) { + if (customerId != null && !adoption.getCustomer().getId().equals(customerId)) { throw new ResourceNotFoundException("You can only view your own adoptions"); } @@ -78,9 +70,9 @@ public class AdoptionService { Pet pet = petRepository.findById(request.getPetId()) .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId())); - Customer customer = customerRepository.findById(request.getCustomerId()) + User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); - Employee employee = resolveAdoptionEmployee(request.getEmployeeId()); + User employee = resolveAdoptionEmployee(request.getEmployeeId()); String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, null); @@ -104,9 +96,9 @@ public class AdoptionService { Pet pet = petRepository.findById(request.getPetId()) .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId())); - Customer customer = customerRepository.findById(request.getCustomerId()) + User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); - Employee employee = resolveAdoptionEmployee(request.getEmployeeId()); + User employee = resolveAdoptionEmployee(request.getEmployeeId()); String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, adoption.getAdoptionId()); @@ -139,9 +131,9 @@ public class AdoptionService { adoption.getAdoptionId(), adoption.getPet().getPetId(), adoption.getPet().getPetName(), - adoption.getCustomer().getCustomerId(), + adoption.getCustomer().getId(), adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(), - adoption.getEmployee().getEmployeeId(), + adoption.getEmployee().getId(), adoption.getEmployee().getFirstName() + " " + adoption.getEmployee().getLastName(), adoption.getAdoptionDate(), adoption.getAdoptionStatus(), @@ -151,31 +143,22 @@ public class AdoptionService { ); } - private Employee resolveAdoptionEmployee(Long requestedEmployeeId) { + private User resolveAdoptionEmployee(Long requestedEmployeeId) { if (requestedEmployeeId != null) { - Employee employee = employeeRepository.findById(requestedEmployeeId) + User employee = userRepository.findById(requestedEmployeeId) .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId)); - if (!isAssignableEmployee(employee)) { + if (!isAssignableUser(employee)) { throw new IllegalArgumentException("Selected employee is not assignable for adoption work"); } return employee; } - return employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc().stream() - .filter(this::isAssignableEmployee) - .findFirst() + return userRepository.findFirstByRoleAndActiveTrueOrderByIdAsc(User.Role.STAFF) .orElseThrow(() -> new IllegalArgumentException("No assignable staff member is available for adoption assignment")); } - private boolean isAssignableEmployee(Employee employee) { - Long userId = employee.getUserId(); - if (userId == null || !Boolean.TRUE.equals(employee.getIsActive())) { - return false; - } - return userRepository.findById(userId) - .filter(user -> user.getRole() == User.Role.STAFF) - .filter(user -> Boolean.TRUE.equals(user.getActive())) - .isPresent(); + private boolean isAssignableUser(User user) { + return user.getRole() == User.Role.STAFF && Boolean.TRUE.equals(user.getActive()); } private String normalizeAdoptionStatus(String adoptionStatus) { 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 c14a9511..f4841228 100644 --- a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -1,12 +1,10 @@ package com.petshop.backend.service; import com.petshop.backend.dto.analytics.DashboardResponse; -import com.petshop.backend.entity.Employee; import com.petshop.backend.entity.Inventory; import com.petshop.backend.entity.Product; import com.petshop.backend.entity.Sale; import com.petshop.backend.entity.User; -import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.SaleRepository; @@ -26,14 +24,12 @@ public class AnalyticsService { private final SaleRepository saleRepository; private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; - private final EmployeeRepository employeeRepository; public AnalyticsService(SaleRepository saleRepository, - InventoryRepository inventoryRepository, ProductRepository productRepository, EmployeeRepository employeeRepository) { + InventoryRepository inventoryRepository, ProductRepository productRepository) { this.saleRepository = saleRepository; this.inventoryRepository = inventoryRepository; this.productRepository = productRepository; - this.employeeRepository = employeeRepository; } @Transactional(readOnly = true) @@ -183,11 +179,8 @@ public class AnalyticsService { } if (user.getRole() == User.Role.STAFF && employeeRevenue.isEmpty()) { - Employee employee = employeeRepository.findByUserId(user.getId()).orElse(null); - if (employee != null) { - String employeeName = employee.getFirstName() + " " + employee.getLastName(); - employeeRevenue.put(employeeName, BigDecimal.ZERO); - } + String employeeName = user.getFirstName() + " " + user.getLastName(); + employeeRevenue.put(employeeName, BigDecimal.ZERO); } return employeeRevenue.entrySet().stream() @@ -200,7 +193,7 @@ public class AnalyticsService { return true; } if (user.getRole() == User.Role.STAFF) { - return sale.getEmployee() != null && sale.getEmployee().getUserId() != null && sale.getEmployee().getUserId().equals(user.getId()); + return sale.getEmployee() != null && sale.getEmployee().getId() != null && sale.getEmployee().getId().equals(user.getId()); } return false; } 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 155b7524..d3a8594e 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -4,19 +4,11 @@ import com.petshop.backend.dto.appointment.AppointmentRequest; import com.petshop.backend.dto.appointment.AppointmentResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Appointment; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AppointmentRepository; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.ServiceRepository; import com.petshop.backend.repository.StoreRepository; @@ -41,25 +33,17 @@ import java.util.stream.Collectors; public class AppointmentService { private final AppointmentRepository appointmentRepository; - private final CustomerRepository customerRepository; - private final CustomerPetRepository customerPetRepository; private final ServiceRepository serviceRepository; private final PetRepository petRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; - private final EmployeeRepository employeeRepository; - private final EmployeeStoreRepository employeeStoreRepository; - public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, CustomerPetRepository customerPetRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) { + public AppointmentService(AppointmentRepository appointmentRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository) { this.appointmentRepository = appointmentRepository; - this.customerRepository = customerRepository; - this.customerPetRepository = customerPetRepository; this.serviceRepository = serviceRepository; this.petRepository = petRepository; this.storeRepository = storeRepository; this.userRepository = userRepository; - this.employeeRepository = employeeRepository; - this.employeeStoreRepository = employeeStoreRepository; } @Transactional(readOnly = true) @@ -70,7 +54,7 @@ public class AppointmentService { if (query != null && !query.trim().isEmpty()) { appointments = appointmentRepository.searchAppointmentsByCustomer(customerId, query, pageable); } else { - appointments = appointmentRepository.findByCustomerCustomerId(customerId, pageable); + appointments = appointmentRepository.findByCustomerId(customerId, pageable); } } else { if (query != null && !query.trim().isEmpty()) { @@ -88,7 +72,7 @@ public class AppointmentService { Appointment appointment = appointmentRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); - if (customerId != null && !appointment.getCustomer().getCustomerId().equals(customerId)) { + if (customerId != null && !appointment.getCustomer().getId().equals(customerId)) { throw new ResourceNotFoundException("You can only view your own appointments"); } @@ -101,7 +85,7 @@ public class AppointmentService { User authenticatedUser = AuthenticationHelper.getAuthenticatedUser(userRepository); - Customer customer = customerRepository.findById(request.getCustomerId()) + User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); StoreLocation store = storeRepository.findById(request.getStoreId()) @@ -111,15 +95,13 @@ public class AppointmentService { .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty(); - boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty(); - if (!hasPetIds && !hasCustomerPetIds) { + if (!hasPetIds) { throw new IllegalArgumentException("Please specify at least one pet."); } - Set pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>(); - Set customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>(); - Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); + Set pets = fetchPets(request.getPetIds()); + User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null); @@ -132,7 +114,6 @@ public class AppointmentService { appointment.setAppointmentTime(request.getAppointmentTime()); appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setPets(pets); - appointment.setCustomerPets(customerPets); appointment.setEmployee(employee); appointment = appointmentRepository.save(appointment); @@ -148,7 +129,7 @@ public class AppointmentService { Appointment appointment = appointmentRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); - Customer customer = customerRepository.findById(request.getCustomerId()) + User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); StoreLocation store = storeRepository.findById(request.getStoreId()) @@ -158,15 +139,13 @@ public class AppointmentService { .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty(); - boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty(); - if (!hasPetIds && !hasCustomerPetIds) { + if (!hasPetIds) { throw new IllegalArgumentException("Please specify at least one pet."); } - Set pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>(); - Set customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>(); - Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); + Set pets = fetchPets(request.getPetIds()); + User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), id); @@ -178,7 +157,6 @@ public class AppointmentService { appointment.setAppointmentTime(request.getAppointmentTime()); appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setPets(pets); - appointment.setCustomerPets(customerPets); appointment.setEmployee(employee); appointment = appointmentRepository.save(appointment); @@ -206,20 +184,17 @@ public class AppointmentService { com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId)); - List assignableEmployees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream() - .filter(es -> isAssignableEmployee(es.getEmployee())) - .map(EmployeeStore::getEmployee) - .collect(Collectors.toList()); + List assignableUsers = userRepository.findByPrimaryStoreStoreIdAndRoleAndActiveTrue(storeId, User.Role.STAFF); - if (assignableEmployees.isEmpty()) { + if (assignableUsers.isEmpty()) { return List.of(); } - List employeeIds = assignableEmployees.stream().map(Employee::getEmployeeId).collect(Collectors.toList()); - List allAppointments = appointmentRepository.findByEmployeeEmployeeIdInAndAppointmentDate(employeeIds, date); - + List employeeIds = assignableUsers.stream().map(User::getId).collect(Collectors.toList()); + List allAppointments = appointmentRepository.findByEmployeeIdInAndAppointmentDate(employeeIds, date); + java.util.Map> appointmentsByEmployee = allAppointments.stream() - .collect(Collectors.groupingBy(a -> a.getEmployee().getEmployeeId())); + .collect(Collectors.groupingBy(a -> a.getEmployee().getId())); List availableSlots = new ArrayList<>(); LocalTime startTime = LocalTime.of(9, 0); @@ -229,8 +204,8 @@ public class AppointmentService { LocalTime currentTime = startTime; while (!currentTime.isAfter(latestStart)) { final LocalTime slotTime = currentTime; - boolean anyEmployeeAvailable = assignableEmployees.stream().anyMatch(emp -> { - List empAppointments = appointmentsByEmployee.getOrDefault(emp.getEmployeeId(), List.of()); + boolean anyEmployeeAvailable = assignableUsers.stream().anyMatch(emp -> { + List empAppointments = appointmentsByEmployee.getOrDefault(emp.getId(), List.of()); return isSlotAvailable(empAppointments, service, slotTime, null); }); @@ -262,20 +237,6 @@ public class AppointmentService { return pets; } - private Set fetchCustomerPets(List customerPetIds, Long customerId) { - Set customerPets = new HashSet<>(); - for (Long customerPetId : customerPetIds) { - CustomerPet customerPet = customerPetRepository.findById(customerPetId) - .orElseThrow(() -> new ResourceNotFoundException("Customer pet not found with id: " + customerPetId)); - if (!customerPet.getCustomer().getCustomerId().equals(customerId)) { - throw new IllegalArgumentException("Selected pet does not belong to the selected customer"); - } - customerPets.add(customerPet); - } - - return customerPets; - } - private AppointmentResponse mapToResponse(Appointment appointment) { List petNames = appointment.getPets().stream() .map(Pet::getPetName) @@ -285,17 +246,9 @@ public class AppointmentService { .map(Pet::getPetId) .collect(Collectors.toList()); - List customerPetNames = appointment.getCustomerPets().stream() - .map(CustomerPet::getPetName) - .collect(Collectors.toList()); - - List customerPetIds = appointment.getCustomerPets().stream() - .map(CustomerPet::getCustomerPetId) - .collect(Collectors.toList()); - AppointmentResponse response = new AppointmentResponse(); response.setAppointmentId(appointment.getAppointmentId()); - response.setCustomerId(appointment.getCustomer().getCustomerId()); + response.setCustomerId(appointment.getCustomer().getId()); response.setCustomerName(appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName()); response.setStoreId(appointment.getStore().getStoreId()); response.setStoreName(appointment.getStore().getStoreName()); @@ -304,54 +257,38 @@ public class AppointmentService { response.setAppointmentDate(appointment.getAppointmentDate()); response.setAppointmentTime(appointment.getAppointmentTime()); response.setAppointmentStatus(appointment.getAppointmentStatus()); - response.setEmployeeId(appointment.getEmployee().getEmployeeId()); + response.setEmployeeId(appointment.getEmployee().getId()); response.setEmployeeName(appointment.getEmployee().getFirstName() + " " + appointment.getEmployee().getLastName()); response.setPetNames(petNames); response.setPetIds(petIds); - response.setCustomerPetNames(customerPetNames); - response.setCustomerPetIds(customerPetIds); response.setCreatedAt(appointment.getCreatedAt()); response.setUpdatedAt(appointment.getUpdatedAt()); - + return response; } - private Employee resolveAppointmentEmployee(Long requestedEmployeeId, Long storeId) { - List assignableEmployees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream() - .filter(es -> isAssignableEmployee(es.getEmployee())) - .collect(Collectors.toList()); + private User resolveAppointmentEmployee(Long requestedEmployeeId, Long storeId) { + List assignableUsers = userRepository.findByPrimaryStoreStoreIdAndRoleAndActiveTrue(storeId, User.Role.STAFF); if (requestedEmployeeId != null) { - Employee employee = employeeRepository.findById(requestedEmployeeId) + User employee = userRepository.findById(requestedEmployeeId) .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId)); - boolean assignedToStore = assignableEmployees.stream() - .anyMatch(es -> es.getEmployee().getEmployeeId().equals(requestedEmployeeId)); + boolean assignedToStore = assignableUsers.stream() + .anyMatch(u -> u.getId().equals(requestedEmployeeId)); if (!assignedToStore) { throw new IllegalArgumentException("Selected employee is not assignable for the selected store"); } return employee; } - return assignableEmployees.stream() - .map(EmployeeStore::getEmployee) + return assignableUsers.stream() .findFirst() .orElseThrow(() -> new IllegalArgumentException("No assignable staff member is assigned to the selected store")); } - private boolean isAssignableEmployee(Employee employee) { - Long userId = employee.getUserId(); - if (userId == null || !Boolean.TRUE.equals(employee.getIsActive())) { - return false; - } - return userRepository.findById(userId) - .filter(user -> user.getRole() == User.Role.STAFF) - .filter(user -> Boolean.TRUE.equals(user.getActive())) - .isPresent(); - } - - private void validateAvailability(Employee employee, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) { + private void validateAvailability(User employee, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) { List existingAppointments = appointmentRepository - .findByEmployeeEmployeeIdAndAppointmentDate(employee.getEmployeeId(), date); + .findByEmployeeIdAndAppointmentDate(employee.getId(), date); if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) { throw new IllegalArgumentException("The selected employee is already booked for this time slot"); } @@ -377,11 +314,7 @@ public class AppointmentService { return; } - Employee employee = AuthenticationHelper.getAuthenticatedEmployee(userRepository, employeeRepository); - EmployeeStore employeeStore = employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()) - .orElseThrow(() -> new AccessDeniedException("Authenticated staff member is not assigned to a store")); - - if (!employeeStore.getStore().getStoreId().equals(requestedStoreId)) { + if (user.getPrimaryStore() == null || !user.getPrimaryStore().getStoreId().equals(requestedStoreId)) { throw new AccessDeniedException("Staff can only manage appointments for their assigned store"); } } 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 9b835823..b5b808c2 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatRealtimeService.java @@ -3,11 +3,9 @@ package com.petshop.backend.service; import com.petshop.backend.dto.chat.ConversationResponse; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.Conversation; -import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ConversationRepository; -import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.MessageRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -21,14 +19,12 @@ public class ChatRealtimeService { private final SimpMessagingTemplate messagingTemplate; private final ConversationRepository conversationRepository; private final MessageRepository messageRepository; - private final CustomerRepository customerRepository; private final UserRepository userRepository; - public ChatRealtimeService(SimpMessagingTemplate messagingTemplate, ConversationRepository conversationRepository, MessageRepository messageRepository, CustomerRepository customerRepository, UserRepository userRepository) { + public ChatRealtimeService(SimpMessagingTemplate messagingTemplate, ConversationRepository conversationRepository, MessageRepository messageRepository, UserRepository userRepository) { this.messagingTemplate = messagingTemplate; this.conversationRepository = conversationRepository; this.messageRepository = messageRepository; - this.customerRepository = customerRepository; this.userRepository = userRepository; } @@ -54,13 +50,11 @@ public class ChatRealtimeService { } private void sendConversationToCustomerQueue(ConversationResponse conversation) { - Customer customer = customerRepository.findById(conversation.getCustomerId()) - .orElseThrow(() -> new ResourceNotFoundException("Customer not found")); - if (customer.getUserId() == null) { + User customerUser = userRepository.findById(conversation.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + if (customerUser.getUsername() == null) { return; } - User customerUser = userRepository.findById(customer.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User not found")); messagingTemplate.convertAndSendToUser(customerUser.getUsername(), "/queue/chat/conversations", conversation); } 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 6ae0c4da..e39d9c55 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -6,12 +6,10 @@ import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.dto.chat.UpdateConversationRequest; import com.petshop.backend.entity.Conversation; -import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.Message; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ConversationRepository; -import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.MessageRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.security.access.AccessDeniedException; @@ -28,16 +26,13 @@ public class ChatService { private final ConversationRepository conversationRepository; private final MessageRepository messageRepository; private final UserRepository userRepository; - private final CustomerRepository customerRepository; public ChatService(ConversationRepository conversationRepository, MessageRepository messageRepository, - UserRepository userRepository, - CustomerRepository customerRepository) { + UserRepository userRepository) { this.conversationRepository = conversationRepository; this.messageRepository = messageRepository; this.userRepository = userRepository; - this.customerRepository = customerRepository; } @Transactional @@ -49,11 +44,8 @@ public class ChatService { throw new AccessDeniedException("Only customers can start new conversations"); } - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - Conversation conversation = new Conversation(); - conversation.setCustomerId(customer.getCustomerId()); + conversation.setCustomerId(userId); conversation.setStatus(Conversation.ConversationStatus.OPEN); conversation.setMode(Conversation.ConversationMode.AUTOMATED); conversation = conversationRepository.save(conversation); @@ -72,9 +64,7 @@ public class ChatService { List conversations; if (role == User.Role.CUSTOMER) { - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - conversations = conversationRepository.findByCustomerId(customer.getCustomerId()); + conversations = conversationRepository.findByCustomerId(userId); } else if (role == User.Role.STAFF) { List assignedToMe = conversationRepository.findByStaffId(userId); List unassigned = conversationRepository.findByStaffIdIsNull(); @@ -225,9 +215,7 @@ public class ChatService { } if (role == User.Role.CUSTOMER) { - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - return conversation.getCustomerId().equals(customer.getCustomerId()); + return conversation.getCustomerId().equals(userId); } if (role == User.Role.STAFF) { diff --git a/backend/src/main/java/com/petshop/backend/service/CustomerPetService.java b/backend/src/main/java/com/petshop/backend/service/CustomerPetService.java deleted file mode 100644 index fa424737..00000000 --- a/backend/src/main/java/com/petshop/backend/service/CustomerPetService.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.customerpet.CustomerPetRequest; -import com.petshop.backend.dto.customerpet.CustomerPetResponse; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.util.AuthenticationHelper; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; - -@Service -public class CustomerPetService { - - private final CustomerPetRepository customerPetRepository; - private final CustomerRepository customerRepository; - private final UserRepository userRepository; - private final CatalogImageStorageService catalogImageStorageService; - - public CustomerPetService(CustomerPetRepository customerPetRepository, - CustomerRepository customerRepository, - UserRepository userRepository, - CatalogImageStorageService catalogImageStorageService) { - this.customerPetRepository = customerPetRepository; - this.customerRepository = customerRepository; - this.userRepository = userRepository; - this.catalogImageStorageService = catalogImageStorageService; - } - - @Transactional(readOnly = true) - public List getMyPets() { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - - return customerPetRepository.findByCustomerCustomerIdOrderByCreatedAtDesc(customer.getCustomerId()) - .stream() - .map(this::mapToResponse) - .collect(Collectors.toList()); - } - - @Transactional - public CustomerPetResponse createPet(CustomerPetRequest request) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - - CustomerPet pet = new CustomerPet(); - pet.setCustomer(customer); - pet.setPetName(request.getPetName()); - pet.setSpecies(request.getSpecies()); - pet.setBreed(request.getBreed()); - - pet = customerPetRepository.save(pet); - - return mapToResponse(pet); - } - - @Transactional - public CustomerPetResponse updatePet(Long id, CustomerPetRequest request) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()) - .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); - - pet.setPetName(request.getPetName()); - pet.setSpecies(request.getSpecies()); - pet.setBreed(request.getBreed()); - - pet = customerPetRepository.save(pet); - - return mapToResponse(pet); - } - - @Transactional - public void deletePet(Long id) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); - deleteStoredImageIfPresent(pet.getImageUrl()); - - customerPetRepository.delete(pet); - } - - @Transactional - public CustomerPetResponse uploadImage(Long id, MultipartFile file) throws IOException { - validateImageFile(file); - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); - deleteStoredImageIfPresent(pet.getImageUrl()); - pet.setImageUrl(catalogImageStorageService.storePetImage(file)); - - return mapToResponse(customerPetRepository.save(pet)); - } - - @Transactional - public CustomerPetResponse deleteImage(Long id) { - Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); - CustomerPet pet = customerPetRepository.findByCustomerPetIdAndCustomerCustomerId(id, customer.getCustomerId()).orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); - deleteStoredImageIfPresent(pet.getImageUrl()); - pet.setImageUrl(null); - - return mapToResponse(customerPetRepository.save(pet)); - } - - private CustomerPetResponse mapToResponse(CustomerPet pet) { - return new CustomerPetResponse( - pet.getCustomerPetId(), - pet.getCustomer().getCustomerId(), - pet.getPetName(), - pet.getSpecies(), - pet.getBreed(), - pet.getImageUrl() != null && !pet.getImageUrl().isBlank() - ? "/api/v1/my-pets/" + pet.getCustomerPetId() + "/image" - : null, - pet.getCreatedAt(), - pet.getUpdatedAt() - ); - } - - private void validateImageFile(MultipartFile file) { - if (file == null || file.isEmpty()) { - - throw new IllegalArgumentException("Please select an image to upload"); - } - - if (file.getSize() > 5 * 1024 * 1024) { - - throw new IllegalArgumentException("Image file size must be less than 5MB"); - } - - String contentType = file.getContentType(); - - if (contentType == null) { - - throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed"); - } - - String normalized = contentType.toLowerCase(Locale.ROOT); - - if (!normalized.equals("image/jpeg") && !normalized.equals("image/png") && !normalized.equals("image/gif")) { - - throw new IllegalArgumentException("Only JPG, PNG, and GIF images are allowed"); - } - } - - private void deleteStoredImageIfPresent(String storedImagePath) { - if (storedImagePath == null || storedImagePath.isBlank()) { - - return; - } - - try { - catalogImageStorageService.deletePetImage(storedImagePath); - } - - catch (IOException ignored) { - } - } -} diff --git a/backend/src/main/java/com/petshop/backend/service/CustomerService.java b/backend/src/main/java/com/petshop/backend/service/CustomerService.java deleted file mode 100644 index 33c731d1..00000000 --- a/backend/src/main/java/com/petshop/backend/service/CustomerService.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.common.BulkDeleteRequest; -import com.petshop.backend.dto.customer.CustomerRequest; -import com.petshop.backend.dto.customer.CustomerResponse; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.User; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.UserRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; - -import static org.springframework.http.HttpStatus.CONFLICT; - -@Service -public class CustomerService { - - private static final String TEMP_PASSWORD = "TempPass123!"; - - private final CustomerRepository customerRepository; - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final UserBusinessLinkageService userBusinessLinkageService; - - public CustomerService(CustomerRepository customerRepository, UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { - this.customerRepository = customerRepository; - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.userBusinessLinkageService = userBusinessLinkageService; - } - - public Page getAllCustomers(String query, Pageable pageable) { - Page customers; - if (query != null && !query.trim().isEmpty()) { - customers = customerRepository.searchCustomers(query, pageable); - } else { - customers = customerRepository.findAll(pageable); - } - return customers.map(this::mapToResponse); - } - - public CustomerResponse getCustomerById(Long id) { - Customer customer = customerRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); - return mapToResponse(customer); - } - - @Transactional - public CustomerResponse createCustomer(CustomerRequest request) { - ensureEmailAvailable(request.getEmail(), null); - - Customer customer = new Customer(); - customer.setFirstName(request.getFirstName()); - customer.setLastName(request.getLastName()); - customer.setEmail(request.getEmail()); - - customer = customerRepository.save(customer); - User user = createLinkedUser(customer); - - Customer linkedCustomer = userBusinessLinkageService.ensureLinkedCustomer(user); - syncLinkedUser(linkedCustomer); - return mapToResponse(linkedCustomer); - } - - @Transactional - public CustomerResponse updateCustomer(Long id, CustomerRequest request) { - Customer customer = customerRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); - - ensureEmailAvailable(request.getEmail(), customer.getUserId()); - - customer.setFirstName(request.getFirstName()); - customer.setLastName(request.getLastName()); - customer.setEmail(request.getEmail()); - - customer = customerRepository.save(customer); - syncLinkedUser(customer); - return mapToResponse(customer); - } - - @Transactional - public void deleteCustomer(Long id) { - Customer customer = customerRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); - - if (customer.getUserId() != null && userRepository.existsById(customer.getUserId())) { - userRepository.deleteById(customer.getUserId()); - return; - } - - customerRepository.deleteById(id); - } - - @Transactional - public void bulkDeleteCustomers(BulkDeleteRequest request) { - customerRepository.deleteAllById(request.getIds()); - } - - private CustomerResponse mapToResponse(Customer customer) { - return new CustomerResponse( - customer.getCustomerId(), - customer.getFirstName(), - customer.getLastName(), - customer.getEmail(), - customer.getCreatedAt(), - customer.getUpdatedAt() - ); - } - - private void syncLinkedUser(Customer customer) { - if (customer.getUserId() == null) { - return; - } - userRepository.findById(customer.getUserId()).ifPresent(user -> { - user.setEmail(customer.getEmail()); - user.setFullName((customer.getFirstName() + " " + customer.getLastName()).trim()); - userRepository.save(user); - }); - } - - private User createLinkedUser(Customer customer) { - User user = new User(); - user.setUsername(generateUsername(customer)); - user.setPassword(passwordEncoder.encode(TEMP_PASSWORD)); - user.setEmail(customer.getEmail()); - user.setFullName((customer.getFirstName() + " " + customer.getLastName()).trim()); - user.setPhone(generatePhone(customer)); - user.setRole(User.Role.CUSTOMER); - user.setActive(false); - user.setTokenVersion(0); - return userRepository.save(user); - } - - private String generateUsername(Customer customer) { - return "customer_" + customer.getCustomerId(); - } - - private String generatePhone(Customer customer) { - return String.format("200-000-%04d", customer.getCustomerId()); - } - - private void ensureEmailAvailable(String email, Long currentUserId) { - if (email == null || email.isBlank()) { - return; - } - - userRepository.findByEmail(email).ifPresent(existing -> { - if (currentUserId == null || !existing.getId().equals(currentUserId)) { - throw new ResponseStatusException(CONFLICT, "Email already exists"); - } - }); - } -} diff --git a/backend/src/main/java/com/petshop/backend/service/EmployeeService.java b/backend/src/main/java/com/petshop/backend/service/EmployeeService.java deleted file mode 100644 index baf83bb8..00000000 --- a/backend/src/main/java/com/petshop/backend/service/EmployeeService.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.employee.EmployeeRequest; -import com.petshop.backend.dto.employee.EmployeeResponse; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.User; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.UserRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; - -import static org.springframework.http.HttpStatus.CONFLICT; - -@Service -public class EmployeeService { - private final EmployeeRepository employeeRepository; - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final UserBusinessLinkageService userBusinessLinkageService; - - public EmployeeService(EmployeeRepository employeeRepository, UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { - this.employeeRepository = employeeRepository; - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.userBusinessLinkageService = userBusinessLinkageService; - } - - public Page getAllEmployees(String query, Pageable pageable) { - Page employees; - if (query != null && !query.trim().isEmpty()) { - employees = employeeRepository.searchEmployees(query, pageable); - } else { - employees = employeeRepository.findAll(pageable); - } - return employees.map(this::mapToResponse); - } - - public EmployeeResponse getEmployeeById(Long id) { - Employee employee = employeeRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); - return mapToResponse(employee); - } - - @Transactional - public EmployeeResponse createEmployee(EmployeeRequest request) { - validateRole(request.getRole()); - if (request.getPassword() == null || request.getPassword().trim().length() < 6) { - throw new IllegalArgumentException("Password must be at least 6 characters"); - } - if (userRepository.findByUsername(request.getUsername()).isPresent()) { - throw new ResponseStatusException(CONFLICT, "Username already exists"); - } - if (request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) { - throw new ResponseStatusException(CONFLICT, "Email already exists"); - } - String phone = trimToNull(request.getPhone()); - if (phone != null && userRepository.findByPhone(phone).isPresent()) { - throw new ResponseStatusException(CONFLICT, "Phone already exists"); - } - - User user = new User(); - user.setUsername(request.getUsername()); - user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setFullName(fullName(request)); - user.setEmail(request.getEmail()); - user.setPhone(phone); - user.setRole(request.getRole()); - user.setActive(request.getActive() != null ? request.getActive() : true); - user = userRepository.save(user); - - Employee employee = userBusinessLinkageService.ensureLinkedEmployee(user); - return mapToResponse(employee, user); - } - - @Transactional - public EmployeeResponse updateEmployee(Long id, EmployeeRequest request) { - Employee employee = employeeRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); - User user = requireLinkedUser(employee); - - validateRole(request.getRole()); - if (!user.getUsername().equals(request.getUsername()) && userRepository.findByUsername(request.getUsername()).isPresent()) { - throw new ResponseStatusException(CONFLICT, "Username already exists"); - } - if (!java.util.Objects.equals(user.getEmail(), request.getEmail()) && request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) { - throw new ResponseStatusException(CONFLICT, "Email already exists"); - } - String phone = trimToNull(request.getPhone()); - Long currentUserId = user.getId(); - if (!java.util.Objects.equals(user.getPhone(), phone)) { - userRepository.findByPhone(phone) - .filter(existing -> !existing.getId().equals(currentUserId)) - .ifPresent(existing -> { throw new ResponseStatusException(CONFLICT, "Phone already exists"); }); - } - - user.setUsername(request.getUsername()); - if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { - user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setTokenVersion(user.getTokenVersion() + 1); - } - user.setEmail(request.getEmail()); - user.setPhone(phone); - user.setFullName(fullName(request)); - user.setRole(request.getRole()); - user.setActive(request.getActive() != null ? request.getActive() : true); - user = userRepository.save(user); - - employee = userBusinessLinkageService.ensureLinkedEmployee(user); - return mapToResponse(employee, user); - } - - @Transactional - public void deleteEmployee(Long id) { - Employee employee = employeeRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); - if (employee.getUserId() != null && userRepository.existsById(employee.getUserId())) { - userRepository.deleteById(employee.getUserId()); - return; - } - employeeRepository.deleteById(id); - } - - private EmployeeResponse mapToResponse(Employee employee) { - User user = employee.getUserId() == null ? null : userRepository.findById(employee.getUserId()).orElse(null); - return mapToResponse(employee, user); - } - - private EmployeeResponse mapToResponse(Employee employee, User user) { - EmployeeResponse response = new EmployeeResponse(); - response.setEmployeeId(employee.getEmployeeId()); - response.setUserId(user != null ? user.getId() : employee.getUserId()); - response.setUsername(user != null ? user.getUsername() : null); - response.setFirstName(employee.getFirstName()); - response.setLastName(employee.getLastName()); - response.setFullName(user != null ? user.getFullName() : fullName(employee)); - response.setEmail(user != null ? user.getEmail() : employee.getEmail()); - response.setPhone(user != null ? user.getPhone() : null); - response.setRole(user != null ? user.getRole().name() : normalizeRole(employee.getRole())); - response.setActive(user != null ? user.getActive() : employee.getIsActive()); - response.setCreatedAt(employee.getCreatedAt()); - response.setUpdatedAt(employee.getUpdatedAt()); - return response; - } - - private User requireLinkedUser(Employee employee) { - if (employee.getUserId() == null) { - throw new ResourceNotFoundException("Employee user account not found"); - } - return userRepository.findById(employee.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("Employee user account not found")); - } - - private void validateRole(User.Role role) { - if (role != User.Role.STAFF && role != User.Role.ADMIN) { - throw new IllegalArgumentException("Employee role must be STAFF or ADMIN"); - } - } - - private String fullName(EmployeeRequest request) { - return (request.getFirstName().trim() + " " + request.getLastName().trim()).trim(); - } - - private String fullName(Employee employee) { - return (employee.getFirstName().trim() + " " + employee.getLastName().trim()).trim(); - } - - private String normalizeRole(String role) { - return role == null ? null : role.trim().toUpperCase(java.util.Locale.ROOT); - } - - private String trimToNull(String value) { - if (value == null) { - return null; - } - String trimmed = value.trim(); - return trimmed.isEmpty() ? null : trimmed; - } -} 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 7d038ed4..ef6ab4c5 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -4,16 +4,15 @@ import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.dto.pet.PetRequest; import com.petshop.backend.dto.pet.PetResponse; import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.StoreRepository; +import com.petshop.backend.repository.UserRepository; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.data.domain.Page; @@ -33,14 +32,14 @@ public class PetService { private final PetRepository petRepository; private final AdoptionRepository adoptionRepository; - private final CustomerRepository customerRepository; + private final UserRepository userRepository; private final StoreRepository storeRepository; private final CatalogImageStorageService catalogImageStorageService; - public PetService(PetRepository petRepository, AdoptionRepository adoptionRepository, CustomerRepository customerRepository, StoreRepository storeRepository, CatalogImageStorageService catalogImageStorageService) { + public PetService(PetRepository petRepository, AdoptionRepository adoptionRepository, UserRepository userRepository, StoreRepository storeRepository, CatalogImageStorageService catalogImageStorageService) { this.petRepository = petRepository; this.adoptionRepository = adoptionRepository; - this.customerRepository = customerRepository; + this.userRepository = userRepository; this.storeRepository = storeRepository; this.catalogImageStorageService = catalogImageStorageService; } @@ -188,7 +187,7 @@ public class PetService { } return adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(pet.getPetId(), "Completed") .map(Adoption::getCustomer) - .map(customer -> userId.equals(customer.getUserId())) + .map(customer -> userId.equals(customer.getId())) .orElse(false); } @@ -257,7 +256,7 @@ public class PetService { } private PetResponse mapToResponse(Pet pet) { - Customer customer = pet.getCustomer(); + User owner = pet.getOwner(); StoreLocation store = pet.getStore(); return new PetResponse( pet.getPetId(), @@ -270,8 +269,8 @@ public class PetService { pet.getImageUrl() != null && !pet.getImageUrl().isBlank() ? "/api/v1/pets/" + pet.getPetId() + "/image" : null, pet.getCreatedAt(), pet.getUpdatedAt(), - customer != null ? customer.getCustomerId() : null, - customer != null ? customer.getFirstName() + " " + customer.getLastName() : null, + owner != null ? owner.getId() : null, + owner != null ? owner.getFirstName() + " " + owner.getLastName() : null, store != null ? store.getStoreId() : null, store != null ? store.getStoreName() : null ); @@ -280,11 +279,11 @@ public class PetService { private void applyOwnerAndStore(Pet pet, PetRequest request) { if ("owned".equalsIgnoreCase(request.getPetStatus())) { if (request.getCustomerId() != null) { - Customer customer = customerRepository.findById(request.getCustomerId()) + User owner = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); - pet.setCustomer(customer); + pet.setOwner(owner); } else { - pet.setCustomer(null); + pet.setOwner(null); } pet.setStore(null); } else if ("available".equalsIgnoreCase(request.getPetStatus()) || "unadopted".equalsIgnoreCase(request.getPetStatus())) { @@ -295,14 +294,14 @@ public class PetService { } else { pet.setStore(null); } - pet.setCustomer(null); + pet.setOwner(null); } else { if (request.getCustomerId() != null) { - Customer customer = customerRepository.findById(request.getCustomerId()) + User owner = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); - pet.setCustomer(customer); + pet.setOwner(owner); } else { - pet.setCustomer(null); + pet.setOwner(null); } if (request.getStoreId() != null) { StoreLocation store = storeRepository.findById(request.getStoreId()) @@ -318,8 +317,8 @@ public class PetService { if (!"owned".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()))) { return false; } - Customer customer = pet.getCustomer(); - return customer != null && userId.equals(customer.getUserId()); + User owner = pet.getOwner(); + return owner != null && userId.equals(owner.getId()); } public record ImagePayload(Resource resource, MediaType mediaType) { 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 61c067fb..6d3ef880 100644 --- a/backend/src/main/java/com/petshop/backend/service/RefundService.java +++ b/backend/src/main/java/com/petshop/backend/service/RefundService.java @@ -46,13 +46,13 @@ public class RefundService { throw new RuntimeException("Sale has no associated customer"); } - if (customerId != null && !sale.getCustomer().getCustomerId().equals(customerId)) { + if (customerId != null && !sale.getCustomer().getId().equals(customerId)) { throw new RuntimeException("You can only create refunds for your own purchases"); } Refund refund = new Refund(); refund.setSaleId(sale.getSaleId()); - refund.setCustomerId(sale.getCustomer().getCustomerId()); + refund.setCustomerId(sale.getCustomer().getId()); refund.setReason(request.getReason()); refund.setStatus(Refund.RefundStatus.PENDING); 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 b8d5861e..acad5e68 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -24,20 +24,14 @@ public class SaleService { private final ProductRepository productRepository; private final StoreRepository storeRepository; private final InventoryRepository inventoryRepository; - private final EmployeeRepository employeeRepository; - private final EmployeeStoreRepository employeeStoreRepository; private final UserRepository userRepository; - private final CustomerRepository customerRepository; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, UserRepository userRepository, CustomerRepository customerRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; - this.employeeRepository = employeeRepository; - this.employeeStoreRepository = employeeStoreRepository; this.userRepository = userRepository; - this.customerRepository = customerRepository; } @Transactional(readOnly = true) @@ -60,18 +54,16 @@ public class SaleService { @Transactional public SaleResponse createSale(SaleRequest request) { - User user = AuthenticationHelper.getAuthenticatedUser(userRepository); - Employee employee = AuthenticationHelper.getAuthenticatedEmployee(userRepository, employeeRepository); - Long employeeStoreId = employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()) - .orElseThrow(() -> new BusinessException("Authenticated staff member is not assigned to a store")) - .getStore() - .getStoreId(); + User employee = AuthenticationHelper.getAuthenticatedUser(userRepository); StoreLocation store = storeRepository.findById(request.getStoreId()) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); - if (user.getRole() == User.Role.STAFF && !employeeStoreId.equals(store.getStoreId())) { - throw new BusinessException("Staff can only create sales for their assigned store"); + if (employee.getRole() == User.Role.STAFF) { + Long assignedStoreId = employee.getPrimaryStore() != null ? employee.getPrimaryStore().getStoreId() : null; + if (!store.getStoreId().equals(assignedStoreId)) { + throw new BusinessException("Staff can only create sales for their assigned store"); + } } Sale sale = new Sale(); @@ -82,7 +74,7 @@ public class SaleService { sale.setIsRefund(request.getIsRefund() != null ? request.getIsRefund() : false); if (request.getCustomerId() != null) { - Customer customer = customerRepository.findById(request.getCustomerId()) + User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); sale.setCustomer(customer); } @@ -185,7 +177,7 @@ public class SaleService { SaleResponse response = new SaleResponse(); response.setSaleId(sale.getSaleId()); response.setSaleDate(sale.getSaleDate()); - response.setEmployeeId(sale.getEmployee().getEmployeeId()); + response.setEmployeeId(sale.getEmployee().getId()); response.setEmployeeName(sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName()); if (sale.getStore() != null) { diff --git a/backend/src/main/java/com/petshop/backend/service/StoreAssignmentService.java b/backend/src/main/java/com/petshop/backend/service/StoreAssignmentService.java deleted file mode 100644 index 31cc18d5..00000000 --- a/backend/src/main/java/com/petshop/backend/service/StoreAssignmentService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; -import com.petshop.backend.entity.StoreLocation; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.EmployeeStoreRepository; -import com.petshop.backend.repository.StoreRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class StoreAssignmentService { - - private final EmployeeStoreRepository employeeStoreRepository; - private final StoreRepository storeRepository; - - public StoreAssignmentService(EmployeeStoreRepository employeeStoreRepository, StoreRepository storeRepository) { - this.employeeStoreRepository = employeeStoreRepository; - this.storeRepository = storeRepository; - } - - @Transactional - public void assignStoreIfMissing(Employee employee, Long storeId) { - if (employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()).isPresent()) { - return; - } - - StoreLocation store = storeRepository.findById(storeId) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); - - employeeStoreRepository.save(new EmployeeStore(employee, store)); - } -} diff --git a/backend/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java b/backend/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java deleted file mode 100644 index 05751688..00000000 --- a/backend/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -public class UserBusinessLinkageService { - - private final EmployeeRepository employeeRepository; - private final CustomerRepository customerRepository; - - @Autowired - public UserBusinessLinkageService(EmployeeRepository employeeRepository, CustomerRepository customerRepository) { - this.employeeRepository = employeeRepository; - this.customerRepository = customerRepository; - } - - @Transactional - public Employee ensureLinkedEmployee(User user) { - if (user.getId() != null) { - var existing = employeeRepository.findByUserId(user.getId()); - if (existing.isPresent()) { - return syncEmployee(existing.get(), user); - } - } - - List emailMatches = employeeRepository.findAllByEmail(user.getEmail()); - - if (emailMatches.size() == 1) { - Employee employee = emailMatches.get(0); - if (employee.getUserId() == null) { - employee.setUserId(user.getId()); - return syncEmployee(employee, user); - } - } - - Employee newEmployee = new Employee(); - newEmployee.setUserId(user.getId()); - newEmployee.setEmail(user.getEmail()); - - String[] nameParts = splitFullName(user.getFullName()); - newEmployee.setFirstName(nameParts[0]); - newEmployee.setLastName(nameParts[1]); - - newEmployee.setIsActive(true); - - if (user.getRole() == User.Role.ADMIN) { - newEmployee.setRole("Manager"); - } else if (user.getRole() == User.Role.STAFF) { - newEmployee.setRole("Staff"); - } else { - newEmployee.setRole("Staff"); - } - - return syncEmployee(newEmployee, user); - } - - @Transactional - public Customer ensureLinkedCustomer(User user) { - if (user.getId() != null) { - var existing = customerRepository.findByUserId(user.getId()); - if (existing.isPresent()) { - return syncCustomer(existing.get(), user); - } - } - - List emailMatches = customerRepository.findAllByEmail(user.getEmail()); - - if (emailMatches.size() == 1) { - Customer customer = emailMatches.get(0); - if (customer.getUserId() == null) { - customer.setUserId(user.getId()); - return syncCustomer(customer, user); - } - } - - Customer newCustomer = new Customer(); - newCustomer.setUserId(user.getId()); - newCustomer.setEmail(user.getEmail()); - - String[] nameParts = splitFullName(user.getFullName()); - newCustomer.setFirstName(nameParts[0]); - newCustomer.setLastName(nameParts[1]); - - return syncCustomer(newCustomer, user); - } - - @Transactional - public void syncLinkedRecords(User user) { - if (user.getRole() == User.Role.CUSTOMER) { - ensureLinkedCustomer(user); - return; - } - ensureLinkedEmployee(user); - } - - private Employee syncEmployee(Employee employee, User user) { - employee.setUserId(user.getId()); - employee.setEmail(user.getEmail()); - String[] nameParts = splitFullName(user.getFullName()); - employee.setFirstName(nameParts[0]); - employee.setLastName(nameParts[1]); - if (user.getRole() == User.Role.ADMIN) { - employee.setRole("Manager"); - } else { - employee.setRole("Staff"); - } - return employeeRepository.save(employee); - } - - private Customer syncCustomer(Customer customer, User user) { - customer.setUserId(user.getId()); - customer.setEmail(user.getEmail()); - String[] nameParts = splitFullName(user.getFullName()); - customer.setFirstName(nameParts[0]); - customer.setLastName(nameParts[1]); - return customerRepository.save(customer); - } - - private String[] splitFullName(String fullName) { - if (fullName == null || fullName.trim().isEmpty()) { - return new String[]{"System", "User"}; - } - - String trimmed = fullName.trim(); - int spaceIndex = trimmed.indexOf(' '); - - if (spaceIndex == -1) { - return new String[]{trimmed, "User"}; - } - - String firstName = trimmed.substring(0, spaceIndex).trim(); - String lastName = trimmed.substring(spaceIndex + 1).trim(); - - if (firstName.isEmpty()) { - firstName = "System"; - } - if (lastName.isEmpty()) { - lastName = "User"; - } - - return new String[]{firstName, lastName}; - } -} 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 e3a17c5c..a6661cf1 100644 --- a/backend/src/main/java/com/petshop/backend/service/UserService.java +++ b/backend/src/main/java/com/petshop/backend/service/UserService.java @@ -26,13 +26,11 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; - private final UserBusinessLinkageService userBusinessLinkageService; private final StoreRepository storeRepository; - public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreRepository storeRepository) { + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, StoreRepository storeRepository) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; - this.userBusinessLinkageService = userBusinessLinkageService; this.storeRepository = storeRepository; } @@ -79,8 +77,6 @@ public class UserService { user = userRepository.save(user); - userBusinessLinkageService.syncLinkedRecords(user); - return mapToResponse(user); } @@ -117,7 +113,6 @@ public class UserService { } user = userRepository.save(user); - userBusinessLinkageService.syncLinkedRecords(user); return mapToResponse(user); } 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 b1ab33a1..66c7418f 100644 --- a/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java +++ b/backend/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -1,10 +1,6 @@ package com.petshop.backend.util; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Employee; import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.AppPrincipal; import org.springframework.security.core.Authentication; @@ -47,16 +43,4 @@ public class AuthenticationHelper { return userRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("User not found: " + username)); } - - public static Employee getAuthenticatedEmployee(UserRepository userRepository, EmployeeRepository employeeRepository) { - User user = getAuthenticatedUser(userRepository); - return employeeRepository.findByUserId(user.getId()) - .orElseThrow(() -> new RuntimeException("Employee record not found for user: " + user.getUsername())); - } - - public static Customer getAuthenticatedCustomer(UserRepository userRepository, CustomerRepository customerRepository) { - User user = getAuthenticatedUser(userRepository); - return customerRepository.findByUserId(user.getId()) - .orElseThrow(() -> new RuntimeException("Customer record not found for user: " + user.getUsername())); - } } -- 2.49.1 From 0482af966e343a027256abb3609bea4a8ad318d6 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:22:26 -0600 Subject: [PATCH 05/15] simplify appointment to single pet --- .../repository/AppointmentRepository.java | 4 +- .../backend/service/AppointmentService.java | 45 +++++-------------- 2 files changed, 12 insertions(+), 37 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 f4cdc564..00edebf9 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -21,7 +21,7 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.store.storeId = :storeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')") List findByStoreAndDate(@Param("storeId") Long storeId, @Param("date") LocalDate date); - @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " + + @Query("SELECT a FROM Appointment a LEFT JOIN a.pet p WHERE " + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + @@ -30,7 +30,7 @@ public interface AppointmentRepository extends JpaRepository Page findByCustomerId(Long customerId, Pageable pageable); - @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE a.customer.id = :customerId AND (" + + @Query("SELECT a FROM Appointment a LEFT JOIN a.pet p WHERE a.customer.id = :customerId AND (" + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + 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 d3a8594e..d7a65149 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -24,9 +24,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; @Service @@ -94,13 +92,7 @@ public class AppointmentService { com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); - boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty(); - - if (!hasPetIds) { - throw new IllegalArgumentException("Please specify at least one pet."); - } - - Set pets = fetchPets(request.getPetIds()); + Pet pet = request.getPetId() != null ? fetchPet(request.getPetId()) : null; User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); @@ -113,7 +105,7 @@ public class AppointmentService { appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); appointment.setAppointmentStatus(request.getAppointmentStatus()); - appointment.setPets(pets); + appointment.setPet(pet); appointment.setEmployee(employee); appointment = appointmentRepository.save(appointment); @@ -138,13 +130,7 @@ public class AppointmentService { com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); - boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty(); - - if (!hasPetIds) { - throw new IllegalArgumentException("Please specify at least one pet."); - } - - Set pets = fetchPets(request.getPetIds()); + Pet pet = request.getPetId() != null ? fetchPet(request.getPetId()) : null; User employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId()); validateStoreAccess(store.getStoreId(), authenticatedUser); @@ -156,7 +142,7 @@ public class AppointmentService { appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); appointment.setAppointmentStatus(request.getAppointmentStatus()); - appointment.setPets(pets); + appointment.setPet(pet); appointment.setEmployee(employee); appointment = appointmentRepository.save(appointment); @@ -227,24 +213,13 @@ public class AppointmentService { } } - private Set fetchPets(List petIds) { - Set pets = new HashSet<>(); - for (Long petId : petIds) { - Pet pet = petRepository.findById(petId) - .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId)); - pets.add(pet); - } - return pets; + private Pet fetchPet(Long petId) { + return petRepository.findById(petId) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId)); } private AppointmentResponse mapToResponse(Appointment appointment) { - List petNames = appointment.getPets().stream() - .map(Pet::getPetName) - .collect(Collectors.toList()); - - List petIds = appointment.getPets().stream() - .map(Pet::getPetId) - .collect(Collectors.toList()); + Pet pet = appointment.getPet(); AppointmentResponse response = new AppointmentResponse(); response.setAppointmentId(appointment.getAppointmentId()); @@ -259,8 +234,8 @@ public class AppointmentService { response.setAppointmentStatus(appointment.getAppointmentStatus()); response.setEmployeeId(appointment.getEmployee().getId()); response.setEmployeeName(appointment.getEmployee().getFirstName() + " " + appointment.getEmployee().getLastName()); - response.setPetNames(petNames); - response.setPetIds(petIds); + response.setPetName(pet != null ? pet.getPetName() : null); + response.setPetId(pet != null ? pet.getPetId() : null); response.setCreatedAt(appointment.getCreatedAt()); response.setUpdatedAt(appointment.getUpdatedAt()); -- 2.49.1 From a74e2ac0efc1b611dfc53b87f29d633c3371f694 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:24:23 -0600 Subject: [PATCH 06/15] add store dimension to inventory --- .../dto/inventory/InventoryRequest.java | 16 +++++++++-- .../dto/inventory/InventoryResponse.java | 28 +++++++++++++++++-- .../com/petshop/backend/entity/Inventory.java | 13 +++++++++ .../repository/InventoryRepository.java | 5 ++-- .../backend/service/InventoryService.java | 21 +++++++++++++- 5 files changed, 75 insertions(+), 8 deletions(-) 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 2dd953c6..7ad02d3a 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 @@ -12,6 +12,8 @@ public class InventoryRequest { @PositiveOrZero(message = "Quantity must be zero or positive") private Integer quantity; + private Long storeId; + public Long getProdId() { return prodId; } @@ -28,18 +30,27 @@ public class InventoryRequest { this.quantity = quantity; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InventoryRequest that = (InventoryRequest) o; return Objects.equals(prodId, that.prodId) && - Objects.equals(quantity, that.quantity); + Objects.equals(quantity, that.quantity) && + Objects.equals(storeId, that.storeId); } @Override public int hashCode() { - return Objects.hash(prodId, quantity); + return Objects.hash(prodId, quantity, storeId); } @Override @@ -47,6 +58,7 @@ public class InventoryRequest { return "InventoryRequest{" + "prodId=" + prodId + ", quantity=" + quantity + + ", storeId=" + storeId + '}'; } } 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 710dcbf4..5879a554 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 @@ -8,6 +8,8 @@ public class InventoryResponse { private Long prodId; private String productName; private String categoryName; + private Long storeId; + private String storeName; private Integer quantity; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -15,11 +17,13 @@ public class InventoryResponse { public InventoryResponse() { } - public InventoryResponse(Long inventoryId, Long prodId, String productName, String categoryName, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) { + public InventoryResponse(Long inventoryId, Long prodId, String productName, String categoryName, Long storeId, String storeName, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) { this.inventoryId = inventoryId; this.prodId = prodId; this.productName = productName; this.categoryName = categoryName; + this.storeId = storeId; + this.storeName = storeName; this.quantity = quantity; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -57,6 +61,22 @@ public class InventoryResponse { this.categoryName = categoryName; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + public Integer getQuantity() { return quantity; } @@ -86,12 +106,12 @@ public class InventoryResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InventoryResponse that = (InventoryResponse) o; - return Objects.equals(inventoryId, that.inventoryId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(quantity, that.quantity) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(inventoryId, that.inventoryId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(quantity, that.quantity) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(inventoryId, prodId, productName, categoryName, quantity, createdAt, updatedAt); + return Objects.hash(inventoryId, prodId, productName, categoryName, storeId, storeName, quantity, createdAt, updatedAt); } @Override @@ -101,6 +121,8 @@ public class InventoryResponse { ", prodId=" + prodId + ", productName='" + productName + '\'' + ", categoryName='" + categoryName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + ", quantity=" + quantity + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + 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 07b93501..ac859c64 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/backend/src/main/java/com/petshop/backend/entity/Inventory.java @@ -19,6 +19,10 @@ public class Inventory { @JoinColumn(name = "prodId", nullable = false) private Product product; + @ManyToOne + @JoinColumn(name = "storeId") + private StoreLocation store; + @Column(nullable = false) private Integer quantity = 0; @@ -57,6 +61,14 @@ public class Inventory { this.product = product; } + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public Integer getQuantity() { return quantity; } @@ -99,6 +111,7 @@ public class Inventory { return "Inventory{" + "inventoryId=" + inventoryId + ", product=" + product + + ", store=" + store + ", quantity=" + quantity + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + 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 0e9d358c..69ff16ed 100644 --- a/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java @@ -16,8 +16,9 @@ public interface InventoryRepository extends JpaRepository { @Query("SELECT i FROM Inventory i WHERE i.product.prodId = :productId") Optional findByProductId(@Param("productId") Long productId); - @Query("SELECT i FROM Inventory i WHERE " + + @Query("SELECT i FROM Inventory i LEFT JOIN i.store s WHERE " + "LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(i.product.category.categoryName) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(i.product.category.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchInventory(@Param("q") String query, Pageable pageable); } 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 ee63aea7..499e8dd5 100644 --- a/backend/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/backend/src/main/java/com/petshop/backend/service/InventoryService.java @@ -5,9 +5,11 @@ import com.petshop.backend.dto.inventory.InventoryRequest; import com.petshop.backend.dto.inventory.InventoryResponse; import com.petshop.backend.entity.Inventory; import com.petshop.backend.entity.Product; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; +import com.petshop.backend.repository.StoreRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -18,10 +20,12 @@ public class InventoryService { private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; + private final StoreRepository storeRepository; - public InventoryService(InventoryRepository inventoryRepository, ProductRepository productRepository) { + public InventoryService(InventoryRepository inventoryRepository, ProductRepository productRepository, StoreRepository storeRepository) { this.inventoryRepository = inventoryRepository; this.productRepository = productRepository; + this.storeRepository = storeRepository; } public Page getAllInventory(String query, Pageable pageable) { @@ -45,8 +49,14 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); + StoreLocation store = request.getStoreId() != null + ? storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())) + : null; + Inventory inventory = new Inventory(); inventory.setProduct(product); + inventory.setStore(store); inventory.setQuantity(request.getQuantity()); inventory = inventoryRepository.save(inventory); @@ -61,7 +71,13 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); + StoreLocation store = request.getStoreId() != null + ? storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())) + : null; + inventory.setProduct(product); + inventory.setStore(store); inventory.setQuantity(request.getQuantity()); inventory = inventoryRepository.save(inventory); @@ -82,11 +98,14 @@ public class InventoryService { } private InventoryResponse mapToResponse(Inventory inventory) { + StoreLocation store = inventory.getStore(); return new InventoryResponse( inventory.getInventoryId(), inventory.getProduct().getProdId(), inventory.getProduct().getProdName(), inventory.getProduct().getCategory().getCategoryName(), + store != null ? store.getStoreId() : null, + store != null ? store.getStoreName() : null, inventory.getQuantity(), inventory.getCreatedAt(), inventory.getUpdatedAt() -- 2.49.1 From f86cf72dd9089f8ebab4163cc12838ebf8538db0 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:25:33 -0600 Subject: [PATCH 07/15] add storeLocation imageUrl --- .../petshop/backend/dto/store/StoreRequest.java | 16 ++++++++++++++-- .../petshop/backend/dto/store/StoreResponse.java | 12 +++++++++++- .../petshop/backend/entity/StoreLocation.java | 11 +++++++++++ .../petshop/backend/service/StoreService.java | 3 +++ 4 files changed, 39 insertions(+), 3 deletions(-) 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 d6b5fc12..5bb68613 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 @@ -18,6 +18,8 @@ public class StoreRequest { @Email(message = "Email must be valid") private String email; + private String imageUrl; + public String getStoreName() { return storeName; } @@ -50,6 +52,14 @@ public class StoreRequest { this.email = email; } + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -58,12 +68,13 @@ public class StoreRequest { return Objects.equals(storeName, that.storeName) && Objects.equals(address, that.address) && Objects.equals(phone, that.phone) && - Objects.equals(email, that.email); + Objects.equals(email, that.email) && + Objects.equals(imageUrl, that.imageUrl); } @Override public int hashCode() { - return Objects.hash(storeName, address, phone, email); + return Objects.hash(storeName, address, phone, email, imageUrl); } @Override @@ -73,6 +84,7 @@ public class StoreRequest { ", address='" + address + '\'' + ", phone='" + phone + '\'' + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + '}'; } } 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 e6fd563e..bf5cafd2 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 @@ -9,17 +9,19 @@ public class StoreResponse { private String address; private String phone; private String email; + private String imageUrl; private LocalDateTime createdAt; public StoreResponse() { } - public StoreResponse(Long storeId, String storeName, String address, String phone, String email, LocalDateTime createdAt) { + public StoreResponse(Long storeId, String storeName, String address, String phone, String email, String imageUrl, LocalDateTime createdAt) { this.storeId = storeId; this.storeName = storeName; this.address = address; this.phone = phone; this.email = email; + this.imageUrl = imageUrl; this.createdAt = createdAt; } @@ -63,6 +65,14 @@ public class StoreResponse { this.email = email; } + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + public LocalDateTime getCreatedAt() { return createdAt; } 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 8c6a9c76..1f79a830 100644 --- a/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java +++ b/backend/src/main/java/com/petshop/backend/entity/StoreLocation.java @@ -28,6 +28,9 @@ public class StoreLocation { @Column(nullable = false, length = 100) private String email; + @Column(length = 500) + private String imageUrl; + @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -89,6 +92,14 @@ public class StoreLocation { this.email = email; } + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + public LocalDateTime getCreatedAt() { return createdAt; } 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 5d2c9ce3..ff9b6581 100644 --- a/backend/src/main/java/com/petshop/backend/service/StoreService.java +++ b/backend/src/main/java/com/petshop/backend/service/StoreService.java @@ -43,6 +43,7 @@ public class StoreService { store.setAddress(request.getAddress()); store.setPhone(request.getPhone()); store.setEmail(request.getEmail()); + store.setImageUrl(request.getImageUrl()); store = storeRepository.save(store); return mapToResponse(store); @@ -57,6 +58,7 @@ public class StoreService { store.setAddress(request.getAddress()); store.setPhone(request.getPhone()); store.setEmail(request.getEmail()); + store.setImageUrl(request.getImageUrl()); store = storeRepository.save(store); return mapToResponse(store); @@ -82,6 +84,7 @@ public class StoreService { store.getAddress(), store.getPhone(), store.getEmail(), + store.getImageUrl(), store.getCreatedAt() ); } -- 2.49.1 From 3f6dc132f477c5c747874a48f096890aa3aea8f6 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:26:42 -0600 Subject: [PATCH 08/15] add purchaseOrder store FK --- .../purchaseorder/PurchaseOrderResponse.java | 28 +++++++++++++++++-- .../petshop/backend/entity/PurchaseOrder.java | 15 ++++++++-- .../backend/service/PurchaseOrderService.java | 4 +++ 3 files changed, 41 insertions(+), 6 deletions(-) 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 bf575d9d..fbf7d6d9 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 @@ -8,6 +8,8 @@ public class PurchaseOrderResponse { private Long purchaseOrderId; private Long supId; private String supplierName; + private Long storeId; + private String storeName; private LocalDate orderDate; private String status; private LocalDateTime createdAt; @@ -16,10 +18,12 @@ public class PurchaseOrderResponse { public PurchaseOrderResponse() { } - public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, Long storeId, String storeName, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { this.purchaseOrderId = purchaseOrderId; this.supId = supId; this.supplierName = supplierName; + this.storeId = storeId; + this.storeName = storeName; this.orderDate = orderDate; this.status = status; this.createdAt = createdAt; @@ -50,6 +54,22 @@ public class PurchaseOrderResponse { this.supplierName = supplierName; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + public LocalDate getOrderDate() { return orderDate; } @@ -87,12 +107,12 @@ public class PurchaseOrderResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PurchaseOrderResponse that = (PurchaseOrderResponse) o; - return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(status, that.status) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(status, that.status) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(purchaseOrderId, supId, supplierName, orderDate, status, createdAt, updatedAt); + return Objects.hash(purchaseOrderId, supId, supplierName, storeId, storeName, orderDate, status, createdAt, updatedAt); } @Override @@ -101,6 +121,8 @@ public class PurchaseOrderResponse { "purchaseOrderId=" + purchaseOrderId + ", supId=" + supId + ", supplierName='" + supplierName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + ", orderDate=" + orderDate + ", status='" + status + '\'' + ", createdAt=" + createdAt + 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 76fc9a9d..aafe3d89 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -4,11 +4,8 @@ import jakarta.persistence.*; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; -import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; @Entity @@ -23,6 +20,10 @@ public class PurchaseOrder { @JoinColumn(name = "supId", nullable = false) private Supplier supplier; + @ManyToOne + @JoinColumn(name = "storeId") + private StoreLocation store; + @Column(nullable = false) private LocalDate orderDate; @@ -65,6 +66,14 @@ public class PurchaseOrder { this.supplier = supplier; } + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public LocalDate getOrderDate() { return orderDate; } 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 97286a9c..e9f7e4a5 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -2,6 +2,7 @@ package com.petshop.backend.service; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; import com.petshop.backend.entity.PurchaseOrder; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.PurchaseOrderRepository; import org.springframework.data.domain.Page; @@ -34,10 +35,13 @@ public class PurchaseOrderService { } private PurchaseOrderResponse mapToResponse(PurchaseOrder purchaseOrder) { + StoreLocation store = purchaseOrder.getStore(); return new PurchaseOrderResponse( purchaseOrder.getPurchaseOrderId(), purchaseOrder.getSupplier().getSupId(), purchaseOrder.getSupplier().getSupCompany(), + store != null ? store.getStoreId() : null, + store != null ? store.getStoreName() : null, purchaseOrder.getOrderDate(), purchaseOrder.getStatus(), purchaseOrder.getCreatedAt(), -- 2.49.1 From 969fbdfe8b89423314837845798421279ad828b0 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:28:31 -0600 Subject: [PATCH 09/15] add adoption sourceStore FK --- .../backend/dto/adoption/AdoptionRequest.java | 16 +++++++++-- .../dto/adoption/AdoptionResponse.java | 28 +++++++++++++++++-- .../com/petshop/backend/entity/Adoption.java | 12 ++++++++ .../backend/service/AdoptionService.java | 19 ++++++++++++- 4 files changed, 69 insertions(+), 6 deletions(-) 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 9a34dff8..d3700bfc 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 @@ -20,6 +20,8 @@ public class AdoptionRequest { private Long employeeId; + private Long sourceStoreId; + public Long getPetId() { return petId; } @@ -60,6 +62,14 @@ public class AdoptionRequest { this.employeeId = employeeId; } + public Long getSourceStoreId() { + return sourceStoreId; + } + + public void setSourceStoreId(Long sourceStoreId) { + this.sourceStoreId = sourceStoreId; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -69,12 +79,13 @@ public class AdoptionRequest { Objects.equals(customerId, that.customerId) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && - Objects.equals(employeeId, that.employeeId); + Objects.equals(employeeId, that.employeeId) && + Objects.equals(sourceStoreId, that.sourceStoreId); } @Override public int hashCode() { - return Objects.hash(petId, customerId, adoptionDate, adoptionStatus, employeeId); + return Objects.hash(petId, customerId, adoptionDate, adoptionStatus, employeeId, sourceStoreId); } @Override @@ -85,6 +96,7 @@ public class AdoptionRequest { ", adoptionDate=" + adoptionDate + ", adoptionStatus='" + adoptionStatus + '\'' + ", employeeId=" + employeeId + + ", sourceStoreId=" + sourceStoreId + '}'; } } 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 43128d23..7e831213 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 @@ -13,6 +13,8 @@ public class AdoptionResponse { private String customerName; private Long employeeId; private String employeeName; + private Long sourceStoreId; + private String sourceStoreName; private LocalDate adoptionDate; private String adoptionStatus; private BigDecimal adoptionFee; @@ -22,7 +24,7 @@ public class AdoptionResponse { public AdoptionResponse() { } - public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, Long employeeId, String employeeName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) { + public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, Long employeeId, String employeeName, Long sourceStoreId, String sourceStoreName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) { this.adoptionId = adoptionId; this.petId = petId; this.petName = petName; @@ -30,6 +32,8 @@ public class AdoptionResponse { this.customerName = customerName; this.employeeId = employeeId; this.employeeName = employeeName; + this.sourceStoreId = sourceStoreId; + this.sourceStoreName = sourceStoreName; this.adoptionDate = adoptionDate; this.adoptionStatus = adoptionStatus; this.adoptionFee = adoptionFee; @@ -93,6 +97,22 @@ public class AdoptionResponse { this.employeeName = employeeName; } + public Long getSourceStoreId() { + return sourceStoreId; + } + + public void setSourceStoreId(Long sourceStoreId) { + this.sourceStoreId = sourceStoreId; + } + + public String getSourceStoreName() { + return sourceStoreName; + } + + public void setSourceStoreName(String sourceStoreName) { + this.sourceStoreName = sourceStoreName; + } + public LocalDate getAdoptionDate() { return adoptionDate; } @@ -138,12 +158,12 @@ public class AdoptionResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AdoptionResponse that = (AdoptionResponse) o; - return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(sourceStoreId, that.sourceStoreId) && Objects.equals(sourceStoreName, that.sourceStoreName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(adoptionId, petId, petName, customerId, customerName, adoptionDate, adoptionStatus, adoptionFee, createdAt, updatedAt); + return Objects.hash(adoptionId, petId, petName, customerId, customerName, sourceStoreId, sourceStoreName, adoptionDate, adoptionStatus, adoptionFee, createdAt, updatedAt); } @Override @@ -154,6 +174,8 @@ public class AdoptionResponse { ", petName='" + petName + '\'' + ", customerId=" + customerId + ", customerName='" + customerName + '\'' + + ", sourceStoreId=" + sourceStoreId + + ", sourceStoreName='" + sourceStoreName + '\'' + ", adoptionDate=" + adoptionDate + ", adoptionStatus='" + adoptionStatus + '\'' + ", adoptionFee=" + adoptionFee + 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 35d4edac..e5dae5aa 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/backend/src/main/java/com/petshop/backend/entity/Adoption.java @@ -28,6 +28,10 @@ public class Adoption { @JoinColumn(name = "employeeId", nullable = false) private User employee; + @ManyToOne + @JoinColumn(name = "sourceStoreId") + private StoreLocation sourceStore; + @Column(nullable = false) private LocalDate adoptionDate; @@ -77,6 +81,14 @@ public class Adoption { this.employee = employee; } + public StoreLocation getSourceStore() { + return sourceStore; + } + + public void setSourceStore(StoreLocation sourceStore) { + this.sourceStore = sourceStore; + } + public LocalDate getAdoptionDate() { return adoptionDate; } 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 caadfbb9..10b3fd02 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -5,10 +5,12 @@ import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Adoption; import com.petshop.backend.entity.Pet; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AdoptionRepository; import com.petshop.backend.repository.PetRepository; +import com.petshop.backend.repository.StoreRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -27,11 +29,13 @@ public class AdoptionService { private final AdoptionRepository adoptionRepository; private final PetRepository petRepository; private final UserRepository userRepository; + private final StoreRepository storeRepository; - public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository) { + public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository) { this.adoptionRepository = adoptionRepository; this.petRepository = petRepository; this.userRepository = userRepository; + this.storeRepository = storeRepository; } public Page getAllAdoptions(String query, Pageable pageable, Long customerId) { @@ -73,6 +77,10 @@ public class AdoptionService { User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); User employee = resolveAdoptionEmployee(request.getEmployeeId()); + StoreLocation sourceStore = request.getSourceStoreId() != null + ? storeRepository.findById(request.getSourceStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId())) + : null; String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, null); @@ -80,6 +88,7 @@ public class AdoptionService { adoption.setPet(pet); adoption.setCustomer(customer); adoption.setEmployee(employee); + adoption.setSourceStore(sourceStore); adoption.setAdoptionDate(request.getAdoptionDate()); adoption.setAdoptionStatus(adoptionStatus); @@ -99,12 +108,17 @@ public class AdoptionService { User customer = userRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); User employee = resolveAdoptionEmployee(request.getEmployeeId()); + StoreLocation sourceStore = request.getSourceStoreId() != null + ? storeRepository.findById(request.getSourceStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId())) + : null; String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus()); validatePetAvailability(pet, adoption.getAdoptionId()); adoption.setPet(pet); adoption.setCustomer(customer); adoption.setEmployee(employee); + adoption.setSourceStore(sourceStore); adoption.setAdoptionDate(request.getAdoptionDate()); adoption.setAdoptionStatus(adoptionStatus); @@ -127,6 +141,7 @@ public class AdoptionService { } private AdoptionResponse mapToResponse(Adoption adoption) { + StoreLocation sourceStore = adoption.getSourceStore(); return new AdoptionResponse( adoption.getAdoptionId(), adoption.getPet().getPetId(), @@ -135,6 +150,8 @@ public class AdoptionService { adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(), adoption.getEmployee().getId(), adoption.getEmployee().getFirstName() + " " + adoption.getEmployee().getLastName(), + sourceStore != null ? sourceStore.getStoreId() : null, + sourceStore != null ? sourceStore.getStoreName() : null, adoption.getAdoptionDate(), adoption.getAdoptionStatus(), adoption.getPet().getPetPrice(), -- 2.49.1 From 682bd12873f7577d20dda48b1145b010e5f2176d Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:30:11 -0600 Subject: [PATCH 10/15] add coupon cart cartItem entities --- .../java/com/petshop/backend/entity/Cart.java | 155 +++++++++++++++++ .../com/petshop/backend/entity/CartItem.java | 121 +++++++++++++ .../com/petshop/backend/entity/Coupon.java | 162 ++++++++++++++++++ .../repository/CartItemRepository.java | 13 ++ .../backend/repository/CartRepository.java | 16 ++ .../backend/repository/CouponRepository.java | 13 ++ 6 files changed, 480 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/entity/Cart.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/CartItem.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Coupon.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/CartRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/CouponRepository.java diff --git a/backend/src/main/java/com/petshop/backend/entity/Cart.java b/backend/src/main/java/com/petshop/backend/entity/Cart.java new file mode 100644 index 00000000..ba0566f8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Cart.java @@ -0,0 +1,155 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "cart") +public class Cart { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long cartId; + + @ManyToOne + @JoinColumn(name = "userId", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "storeId") + private StoreLocation store; + + @ManyToOne + @JoinColumn(name = "couponId") + private Coupon coupon; + + @Column(nullable = false, length = 20) + private String cartStatus = "ACTIVE"; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal subtotalAmount = BigDecimal.ZERO; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal discountAmount = BigDecimal.ZERO; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal totalAmount = BigDecimal.ZERO; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public Cart() { + } + + public Long getCartId() { + return cartId; + } + + public void setCartId(Long cartId) { + this.cartId = cartId; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + + public Coupon getCoupon() { + return coupon; + } + + public void setCoupon(Coupon coupon) { + this.coupon = coupon; + } + + public String getCartStatus() { + return cartStatus; + } + + public void setCartStatus(String cartStatus) { + this.cartStatus = cartStatus; + } + + public BigDecimal getSubtotalAmount() { + return subtotalAmount; + } + + public void setSubtotalAmount(BigDecimal subtotalAmount) { + this.subtotalAmount = subtotalAmount; + } + + public BigDecimal getDiscountAmount() { + return discountAmount; + } + + public void setDiscountAmount(BigDecimal discountAmount) { + this.discountAmount = discountAmount; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Cart cart = (Cart) o; + return Objects.equals(cartId, cart.cartId); + } + + @Override + public int hashCode() { + return Objects.hash(cartId); + } + + @Override + public String toString() { + return "Cart{" + + "cartId=" + cartId + + ", cartStatus='" + cartStatus + '\'' + + ", totalAmount=" + totalAmount + + '}'; + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/CartItem.java b/backend/src/main/java/com/petshop/backend/entity/CartItem.java new file mode 100644 index 00000000..1ad4c144 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/CartItem.java @@ -0,0 +1,121 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "cart_item") +public class CartItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long cartItemId; + + @ManyToOne + @JoinColumn(name = "cartId", nullable = false) + private Cart cart; + + @ManyToOne + @JoinColumn(name = "prodId", nullable = false) + private Product product; + + @Column(nullable = false) + private Integer quantity; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal unitPrice; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public CartItem() { + } + + public Long getCartItemId() { + return cartItemId; + } + + public void setCartItemId(Long cartItemId) { + this.cartItemId = cartItemId; + } + + public Cart getCart() { + return cart; + } + + public void setCart(Cart cart) { + this.cart = cart; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(BigDecimal unitPrice) { + this.unitPrice = unitPrice; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CartItem cartItem = (CartItem) o; + return Objects.equals(cartItemId, cartItem.cartItemId); + } + + @Override + public int hashCode() { + return Objects.hash(cartItemId); + } + + @Override + public String toString() { + return "CartItem{" + + "cartItemId=" + cartItemId + + ", quantity=" + quantity + + ", unitPrice=" + unitPrice + + '}'; + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Coupon.java b/backend/src/main/java/com/petshop/backend/entity/Coupon.java new file mode 100644 index 00000000..ba234f12 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Coupon.java @@ -0,0 +1,162 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "coupon") +public class Coupon { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long couponId; + + @Column(nullable = false, length = 50, unique = true) + private String couponCode; + + @Column(nullable = false, length = 20) + private String discountType; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal discountValue; + + @Column(precision = 10, scale = 2) + private BigDecimal minOrderAmount; + + @Column(nullable = false) + private Boolean active = true; + + private LocalDateTime startsAt; + + private LocalDateTime endsAt; + + private Integer usageLimit; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public Coupon() { + } + + public Long getCouponId() { + return couponId; + } + + public void setCouponId(Long couponId) { + this.couponId = couponId; + } + + public String getCouponCode() { + return couponCode; + } + + public void setCouponCode(String couponCode) { + this.couponCode = couponCode; + } + + public String getDiscountType() { + return discountType; + } + + public void setDiscountType(String discountType) { + this.discountType = discountType; + } + + public BigDecimal getDiscountValue() { + return discountValue; + } + + public void setDiscountValue(BigDecimal discountValue) { + this.discountValue = discountValue; + } + + public BigDecimal getMinOrderAmount() { + return minOrderAmount; + } + + public void setMinOrderAmount(BigDecimal minOrderAmount) { + this.minOrderAmount = minOrderAmount; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getStartsAt() { + return startsAt; + } + + public void setStartsAt(LocalDateTime startsAt) { + this.startsAt = startsAt; + } + + public LocalDateTime getEndsAt() { + return endsAt; + } + + public void setEndsAt(LocalDateTime endsAt) { + this.endsAt = endsAt; + } + + public Integer getUsageLimit() { + return usageLimit; + } + + public void setUsageLimit(Integer usageLimit) { + this.usageLimit = usageLimit; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Coupon coupon = (Coupon) o; + return Objects.equals(couponId, coupon.couponId); + } + + @Override + public int hashCode() { + return Objects.hash(couponId); + } + + @Override + public String toString() { + return "Coupon{" + + "couponId=" + couponId + + ", couponCode='" + couponCode + '\'' + + ", discountType='" + discountType + '\'' + + ", discountValue=" + discountValue + + ", active=" + active + + '}'; + } +} diff --git a/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java b/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java new file mode 100644 index 00000000..ffcdc166 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/CartItemRepository.java @@ -0,0 +1,13 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.CartItem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface CartItemRepository extends JpaRepository { + + List findByCartCartId(Long cartId); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/CartRepository.java b/backend/src/main/java/com/petshop/backend/repository/CartRepository.java new file mode 100644 index 00000000..3f6a974a --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/CartRepository.java @@ -0,0 +1,16 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Cart; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CartRepository extends JpaRepository { + + List findByUserId(Long userId); + + Optional findByUserIdAndCartStatus(Long userId, String cartStatus); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java b/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java new file mode 100644 index 00000000..64870204 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/CouponRepository.java @@ -0,0 +1,13 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Coupon; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface CouponRepository extends JpaRepository { + + Optional findByCouponCode(String couponCode); +} -- 2.49.1 From a4ed9a7afce6c419e22b059d1a6083f31876af22 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:32:05 -0600 Subject: [PATCH 11/15] add sale channel coupon cart columns --- .../petshop/backend/dto/sale/SaleRequest.java | 40 +++++++++- .../backend/dto/sale/SaleResponse.java | 78 ++++++++++++++---- .../java/com/petshop/backend/entity/Sale.java | 79 +++++++++++++++++++ .../petshop/backend/service/SaleService.java | 30 ++++++- 4 files changed, 209 insertions(+), 18 deletions(-) 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 9c7102f4..081ab05d 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 @@ -22,6 +22,12 @@ public class SaleRequest { private Long customerId; + private String channel; + + private Long couponId; + + private Long cartId; + public Long getStoreId() { return storeId; } @@ -70,6 +76,30 @@ public class SaleRequest { this.customerId = customerId; } + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public Long getCouponId() { + return couponId; + } + + public void setCouponId(Long couponId) { + this.couponId = couponId; + } + + public Long getCartId() { + return cartId; + } + + public void setCartId(Long cartId) { + this.cartId = cartId; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -80,12 +110,15 @@ public class SaleRequest { Objects.equals(items, that.items) && Objects.equals(isRefund, that.isRefund) && Objects.equals(originalSaleId, that.originalSaleId) && - Objects.equals(customerId, that.customerId); + Objects.equals(customerId, that.customerId) && + Objects.equals(channel, that.channel) && + Objects.equals(couponId, that.couponId) && + Objects.equals(cartId, that.cartId); } @Override public int hashCode() { - return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId); + return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId, channel, couponId, cartId); } @Override @@ -97,6 +130,9 @@ public class SaleRequest { ", isRefund=" + isRefund + ", originalSaleId=" + originalSaleId + ", customerId=" + customerId + + ", channel='" + channel + '\'' + + ", couponId=" + couponId + + ", cartId=" + cartId + '}'; } } 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 969b28d3..6523505c 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 @@ -13,6 +13,13 @@ public class SaleResponse { private Long storeId; private String storeName; private BigDecimal totalAmount; + private BigDecimal subtotalAmount; + private BigDecimal couponDiscountAmount; + private BigDecimal employeeDiscountAmount; + private Integer pointsEarned; + private String channel; + private Long couponId; + private Long cartId; private String paymentMethod; private Boolean isRefund; private Long originalSaleId; @@ -22,21 +29,6 @@ public class SaleResponse { public SaleResponse() { } - public SaleResponse(Long saleId, LocalDateTime saleDate, Long employeeId, String employeeName, Long storeId, String storeName, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Long originalSaleId, List items, LocalDateTime createdAt) { - this.saleId = saleId; - this.saleDate = saleDate; - this.employeeId = employeeId; - this.employeeName = employeeName; - this.storeId = storeId; - this.storeName = storeName; - this.totalAmount = totalAmount; - this.paymentMethod = paymentMethod; - this.isRefund = isRefund; - this.originalSaleId = originalSaleId; - this.items = items; - this.createdAt = createdAt; - } - public Long getSaleId() { return saleId; } @@ -93,6 +85,62 @@ public class SaleResponse { this.totalAmount = totalAmount; } + public BigDecimal getSubtotalAmount() { + return subtotalAmount; + } + + public void setSubtotalAmount(BigDecimal subtotalAmount) { + this.subtotalAmount = subtotalAmount; + } + + public BigDecimal getCouponDiscountAmount() { + return couponDiscountAmount; + } + + public void setCouponDiscountAmount(BigDecimal couponDiscountAmount) { + this.couponDiscountAmount = couponDiscountAmount; + } + + public BigDecimal getEmployeeDiscountAmount() { + return employeeDiscountAmount; + } + + public void setEmployeeDiscountAmount(BigDecimal employeeDiscountAmount) { + this.employeeDiscountAmount = employeeDiscountAmount; + } + + public Integer getPointsEarned() { + return pointsEarned; + } + + public void setPointsEarned(Integer pointsEarned) { + this.pointsEarned = pointsEarned; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public Long getCouponId() { + return couponId; + } + + public void setCouponId(Long couponId) { + this.couponId = couponId; + } + + public Long getCartId() { + return cartId; + } + + public void setCartId(Long cartId) { + this.cartId = cartId; + } + public String getPaymentMethod() { return paymentMethod; } 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 ee1a9d51..3bf4d8bf 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -46,6 +46,29 @@ public class Sale { @JoinColumn(name = "originalSaleId") private Sale originalSale; + @Column(nullable = false, length = 20) + private String channel = "IN_STORE"; + + @ManyToOne + @JoinColumn(name = "cartId") + private Cart cart; + + @ManyToOne + @JoinColumn(name = "couponId") + private Coupon coupon; + + @Column(precision = 10, scale = 2) + private BigDecimal subtotalAmount; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal couponDiscountAmount = BigDecimal.ZERO; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal employeeDiscountAmount = BigDecimal.ZERO; + + @Column(nullable = false) + private Integer pointsEarned = 0; + @OneToMany(mappedBy = "sale", cascade = CascadeType.ALL) private List items = new ArrayList<>(); @@ -132,6 +155,62 @@ public class Sale { this.originalSale = originalSale; } + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public Cart getCart() { + return cart; + } + + public void setCart(Cart cart) { + this.cart = cart; + } + + public Coupon getCoupon() { + return coupon; + } + + public void setCoupon(Coupon coupon) { + this.coupon = coupon; + } + + public BigDecimal getSubtotalAmount() { + return subtotalAmount; + } + + public void setSubtotalAmount(BigDecimal subtotalAmount) { + this.subtotalAmount = subtotalAmount; + } + + public BigDecimal getCouponDiscountAmount() { + return couponDiscountAmount; + } + + public void setCouponDiscountAmount(BigDecimal couponDiscountAmount) { + this.couponDiscountAmount = couponDiscountAmount; + } + + public BigDecimal getEmployeeDiscountAmount() { + return employeeDiscountAmount; + } + + public void setEmployeeDiscountAmount(BigDecimal employeeDiscountAmount) { + this.employeeDiscountAmount = employeeDiscountAmount; + } + + public Integer getPointsEarned() { + return pointsEarned; + } + + public void setPointsEarned(Integer pointsEarned) { + this.pointsEarned = pointsEarned; + } + public List getItems() { return items; } 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 acad5e68..643cfc2a 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -25,13 +25,17 @@ public class SaleService { private final StoreRepository storeRepository; private final InventoryRepository inventoryRepository; private final UserRepository userRepository; + private final CouponRepository couponRepository; + private final CartRepository cartRepository; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository, CouponRepository couponRepository, CartRepository cartRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; this.userRepository = userRepository; + this.couponRepository = couponRepository; + this.cartRepository = cartRepository; } @Transactional(readOnly = true) @@ -72,6 +76,19 @@ 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"); + + if (request.getCouponId() != null) { + Coupon coupon = couponRepository.findById(request.getCouponId()) + .orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId())); + sale.setCoupon(coupon); + } + + if (request.getCartId() != null) { + Cart cart = cartRepository.findById(request.getCartId()) + .orElseThrow(() -> new ResourceNotFoundException("Cart not found with id: " + request.getCartId())); + sale.setCart(cart); + } if (request.getCustomerId() != null) { User customer = userRepository.findById(request.getCustomerId()) @@ -186,6 +203,17 @@ public class SaleService { } response.setTotalAmount(sale.getTotalAmount()); + response.setSubtotalAmount(sale.getSubtotalAmount()); + response.setCouponDiscountAmount(sale.getCouponDiscountAmount()); + response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount()); + response.setPointsEarned(sale.getPointsEarned()); + response.setChannel(sale.getChannel()); + if (sale.getCoupon() != null) { + response.setCouponId(sale.getCoupon().getCouponId()); + } + if (sale.getCart() != null) { + response.setCartId(sale.getCart().getCartId()); + } response.setPaymentMethod(sale.getPaymentMethod()); response.setIsRefund(sale.getIsRefund()); if (sale.getOriginalSale() != null) { -- 2.49.1 From 31a4356d83f7a93763eec304f149edbf5954754e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:34:03 -0600 Subject: [PATCH 12/15] add service species collection --- .../backend/dto/service/ServiceRequest.java | 18 ++++++++++++++++-- .../backend/dto/service/ServiceResponse.java | 18 +++++++++++++++--- .../com/petshop/backend/entity/Service.java | 15 +++++++++++++++ .../backend/service/ServiceService.java | 7 +++++++ 4 files changed, 53 insertions(+), 5 deletions(-) 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 6b4550ec..c84ac5f7 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 @@ -4,7 +4,9 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import java.math.BigDecimal; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; public class ServiceRequest { @NotBlank(message = "Service name is required") @@ -19,6 +21,8 @@ public class ServiceRequest { @Positive(message = "Duration must be positive") private Integer serviceDuration; + private Set species = new HashSet<>(); + public String getServiceName() { return serviceName; } @@ -51,6 +55,14 @@ public class ServiceRequest { this.serviceDuration = serviceDuration; } + public Set getSpecies() { + return species; + } + + public void setSpecies(Set species) { + this.species = species; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -59,12 +71,13 @@ public class ServiceRequest { return Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && - Objects.equals(serviceDuration, that.serviceDuration); + Objects.equals(serviceDuration, that.serviceDuration) && + Objects.equals(species, that.species); } @Override public int hashCode() { - return Objects.hash(serviceName, serviceDesc, servicePrice, serviceDuration); + return Objects.hash(serviceName, serviceDesc, servicePrice, serviceDuration, species); } @Override @@ -74,6 +87,7 @@ public class ServiceRequest { ", serviceDesc='" + serviceDesc + '\'' + ", servicePrice=" + servicePrice + ", serviceDuration=" + serviceDuration + + ", species=" + species + '}'; } } 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 53a2be5b..4f0300ab 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 @@ -3,6 +3,7 @@ package com.petshop.backend.dto.service; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Objects; +import java.util.Set; public class ServiceResponse { private Long serviceId; @@ -10,18 +11,20 @@ public class ServiceResponse { private String serviceDesc; private BigDecimal servicePrice; private Integer serviceDuration; + private Set species; private LocalDateTime createdAt; private LocalDateTime updatedAt; public ServiceResponse() { } - public ServiceResponse(Long serviceId, String serviceName, String serviceDesc, BigDecimal servicePrice, Integer serviceDuration, LocalDateTime createdAt, LocalDateTime updatedAt) { + public ServiceResponse(Long serviceId, String serviceName, String serviceDesc, BigDecimal servicePrice, Integer serviceDuration, Set species, LocalDateTime createdAt, LocalDateTime updatedAt) { this.serviceId = serviceId; this.serviceName = serviceName; this.serviceDesc = serviceDesc; this.servicePrice = servicePrice; this.serviceDuration = serviceDuration; + this.species = species; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -66,6 +69,14 @@ public class ServiceResponse { this.serviceDuration = serviceDuration; } + public Set getSpecies() { + return species; + } + + public void setSpecies(Set species) { + this.species = species; + } + public LocalDateTime getCreatedAt() { return createdAt; } @@ -87,12 +98,12 @@ public class ServiceResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ServiceResponse that = (ServiceResponse) o; - return Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDuration, that.serviceDuration) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDuration, that.serviceDuration) && Objects.equals(species, that.species) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(serviceId, serviceName, serviceDesc, servicePrice, serviceDuration, createdAt, updatedAt); + return Objects.hash(serviceId, serviceName, serviceDesc, servicePrice, serviceDuration, species, createdAt, updatedAt); } @Override @@ -103,6 +114,7 @@ public class ServiceResponse { ", serviceDesc='" + serviceDesc + '\'' + ", servicePrice=" + servicePrice + ", serviceDuration=" + serviceDuration + + ", species=" + species + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; 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 a73387c8..77223922 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Service.java +++ b/backend/src/main/java/com/petshop/backend/entity/Service.java @@ -6,7 +6,9 @@ import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; @Entity @Table(name = "service") @@ -28,6 +30,11 @@ public class Service { @Column(nullable = false) private Integer serviceDuration; + @ElementCollection + @CollectionTable(name = "service_species", joinColumns = @JoinColumn(name = "serviceId")) + @Column(name = "species", length = 50) + private Set species = new HashSet<>(); + @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -89,6 +96,14 @@ public class Service { this.serviceDuration = serviceDuration; } + public Set getSpecies() { + return species; + } + + public void setSpecies(Set species) { + this.species = species; + } + public LocalDateTime getCreatedAt() { return createdAt; } 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 5243f101..e2395392 100644 --- a/backend/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/backend/src/main/java/com/petshop/backend/service/ServiceService.java @@ -42,6 +42,9 @@ public class ServiceService { service.setServiceDesc(request.getServiceDesc()); service.setServicePrice(request.getServicePrice()); service.setServiceDuration(request.getServiceDuration()); + if (request.getSpecies() != null) { + service.setSpecies(request.getSpecies()); + } service = serviceRepository.save(service); return mapToResponse(service); @@ -56,6 +59,9 @@ public class ServiceService { service.setServiceDesc(request.getServiceDesc()); service.setServicePrice(request.getServicePrice()); service.setServiceDuration(request.getServiceDuration()); + if (request.getSpecies() != null) { + service.setSpecies(request.getSpecies()); + } service = serviceRepository.save(service); return mapToResponse(service); @@ -81,6 +87,7 @@ public class ServiceService { service.getServiceDesc(), service.getServicePrice(), service.getServiceDuration(), + service.getSpecies(), service.getCreatedAt(), service.getUpdatedAt() ); -- 2.49.1 From 6bde4f4e471d7277e435e72aacccde62e5a1918b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:38:29 -0600 Subject: [PATCH 13/15] add message attachment fields --- .../backend/dto/chat/MessageRequest.java | 43 +++++++++++++++--- .../backend/dto/chat/MessageResponse.java | 40 +++++++++++++++++ .../com/petshop/backend/entity/Message.java | 45 ++++++++++++++++++- .../petshop/backend/service/ChatService.java | 16 +++++-- 4 files changed, 132 insertions(+), 12 deletions(-) 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 cb03d310..cecedfbd 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,18 +1,15 @@ package com.petshop.backend.dto.chat; -import jakarta.validation.constraints.NotBlank; - public class MessageRequest { - @NotBlank(message = "Message content is required") private String content; + private String attachmentUrl; + private String attachmentName; + private String attachmentMimeType; + private Long attachmentSizeBytes; public MessageRequest() { } - public MessageRequest(String content) { - this.content = content; - } - public String getContent() { return content; } @@ -20,4 +17,36 @@ public class MessageRequest { public void setContent(String content) { this.content = content; } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentMimeType() { + return attachmentMimeType; + } + + public void setAttachmentMimeType(String attachmentMimeType) { + this.attachmentMimeType = attachmentMimeType; + } + + public Long getAttachmentSizeBytes() { + return attachmentSizeBytes; + } + + public void setAttachmentSizeBytes(Long attachmentSizeBytes) { + this.attachmentSizeBytes = attachmentSizeBytes; + } } 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 25cffae5..fba1c7c8 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 @@ -11,6 +11,10 @@ public class MessageResponse { private String content; private LocalDateTime timestamp; private Boolean isRead; + private String attachmentUrl; + private String attachmentName; + private String attachmentMimeType; + private Long attachmentSizeBytes; public MessageResponse() { } @@ -32,6 +36,10 @@ public class MessageResponse { response.setContent(message.getContent()); response.setTimestamp(message.getTimestamp()); response.setIsRead(message.getIsRead()); + response.setAttachmentUrl(message.getAttachmentUrl()); + response.setAttachmentName(message.getAttachmentName()); + response.setAttachmentMimeType(message.getAttachmentMimeType()); + response.setAttachmentSizeBytes(message.getAttachmentSizeBytes()); return response; } @@ -82,4 +90,36 @@ public class MessageResponse { public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentMimeType() { + return attachmentMimeType; + } + + public void setAttachmentMimeType(String attachmentMimeType) { + this.attachmentMimeType = attachmentMimeType; + } + + public Long getAttachmentSizeBytes() { + return attachmentSizeBytes; + } + + public void setAttachmentSizeBytes(Long attachmentSizeBytes) { + this.attachmentSizeBytes = attachmentSizeBytes; + } } 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 33777bf5..7c7bc498 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Message.java +++ b/backend/src/main/java/com/petshop/backend/entity/Message.java @@ -19,9 +19,20 @@ public class Message { @Column(nullable = false) private Long senderId; - @Column(nullable = false, columnDefinition = "TEXT") + @Column(columnDefinition = "TEXT") private String content; + @Column(length = 255) + private String attachmentUrl; + + @Column(length = 255) + private String attachmentName; + + @Column(length = 100) + private String attachmentMimeType; + + private Long attachmentSizeBytes; + @CreationTimestamp @Column(nullable = false, updatable = false) private LocalDateTime timestamp; @@ -88,4 +99,36 @@ public class Message { public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentMimeType() { + return attachmentMimeType; + } + + public void setAttachmentMimeType(String attachmentMimeType) { + this.attachmentMimeType = attachmentMimeType; + } + + public Long getAttachmentSizeBytes() { + return attachmentSizeBytes; + } + + public void setAttachmentSizeBytes(Long attachmentSizeBytes) { + this.attachmentSizeBytes = attachmentSizeBytes; + } } 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 e39d9c55..e076daed 100644 --- a/backend/src/main/java/com/petshop/backend/service/ChatService.java +++ b/backend/src/main/java/com/petshop/backend/service/ChatService.java @@ -77,7 +77,8 @@ public class ChatService { return conversations.stream() .map(conv -> { List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conv.getId()); - String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1); + String lastMessage = last != null && last.getContent() != null ? last.getContent() : ""; return ConversationResponse.fromEntity(conv, lastMessage); }) .collect(Collectors.toList()); @@ -97,7 +98,8 @@ public class ChatService { } List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); - String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1); + String lastMessage = last != null && last.getContent() != null ? last.getContent() : ""; return ConversationResponse.fromEntity(conversation, lastMessage); } @@ -124,6 +126,10 @@ public class ChatService { message.setConversationId(conversationId); message.setSenderId(userId); message.setContent(request.getContent()); + message.setAttachmentUrl(request.getAttachmentUrl()); + message.setAttachmentName(request.getAttachmentName()); + message.setAttachmentMimeType(request.getAttachmentMimeType()); + message.setAttachmentSizeBytes(request.getAttachmentSizeBytes()); message.setIsRead(false); message = messageRepository.save(message); @@ -158,7 +164,8 @@ public class ChatService { conversationRepository.save(conversation); List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); - String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1); + String lastMessage = last != null && last.getContent() != null ? last.getContent() : ""; return ConversationResponse.fromEntity(conversation, lastMessage); } @@ -180,7 +187,8 @@ public class ChatService { conversation = conversationRepository.save(conversation); List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); - String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + Message last = messages.isEmpty() ? null : messages.get(messages.size() - 1); + String lastMessage = last != null && last.getContent() != null ? last.getContent() : ""; return ConversationResponse.fromEntity(conversation, lastMessage); } -- 2.49.1 From dac5f8c4a61b2b6a10adaa24ab936a07f2463565 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:39:08 -0600 Subject: [PATCH 14/15] add activityLog store FK --- .../java/com/petshop/backend/entity/ActivityLog.java | 12 ++++++++++++ 1 file changed, 12 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 7c778bc3..7445de57 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java +++ b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java @@ -17,6 +17,10 @@ public class ActivityLog { @JoinColumn(name = "userId", nullable = false) private User user; + @ManyToOne + @JoinColumn(name = "storeId") + private StoreLocation store; + @Column(nullable = false, columnDefinition = "TEXT") private String activity; @@ -49,6 +53,14 @@ public class ActivityLog { this.user = user; } + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public String getActivity() { return activity; } -- 2.49.1 From 2420453daaf68c4fae23a9416775a8aeca368bed Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 6 Apr 2026 20:46:27 -0600 Subject: [PATCH 15/15] enable Hibernate validation --- .../backend/RuntimeClasspathValidator.java | 2 +- .../dto/appointment/AppointmentRequest.java | 17 +- .../dto/appointment/AppointmentResponse.java | 35 +- .../petshop/backend/entity/Appointment.java | 22 +- backend/src/main/resources/application.yml | 4 +- .../controller/DropdownControllerTest.java | 201 ----------- .../backend/service/AdoptionServiceTest.java | 150 -------- .../service/AppointmentServiceTest.java | 332 ------------------ .../backend/service/ChatServiceTest.java | 199 ----------- .../backend/service/PetServiceTest.java | 173 --------- 10 files changed, 36 insertions(+), 1099 deletions(-) delete mode 100644 backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java delete mode 100644 backend/src/test/java/com/petshop/backend/service/PetServiceTest.java diff --git a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java index ad18d198..e123f66a 100644 --- a/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java +++ b/backend/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java @@ -11,7 +11,7 @@ final class RuntimeClasspathValidator { if (!resourceExists("application.yml")) { throw new IllegalStateException("Backend resources are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); } - if (!resourceExists("db/migration/V1__baseline_schema.sql")) { + if (!resourceExists("db/migration/V1__target_baseline.sql")) { throw new IllegalStateException("Flyway migration files are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); } } 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 c60c3fcd..9ddb9ad2 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 @@ -3,7 +3,6 @@ package com.petshop.backend.dto.appointment; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalTime; -import java.util.List; import java.util.Objects; public class AppointmentRequest { @@ -25,7 +24,7 @@ public class AppointmentRequest { @NotNull(message = "Appointment status is required") private String appointmentStatus; - private List petIds; + private Long petId; private Long employeeId; @@ -77,12 +76,12 @@ public class AppointmentRequest { this.appointmentStatus = appointmentStatus; } - public List getPetIds() { - return petIds; + public Long getPetId() { + return petId; } - public void setPetIds(List petIds) { - this.petIds = petIds; + public void setPetId(Long petId) { + this.petId = petId; } public Long getEmployeeId() { @@ -104,13 +103,13 @@ public class AppointmentRequest { Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && - Objects.equals(petIds, that.petIds) && + Objects.equals(petId, that.petId) && Objects.equals(employeeId, that.employeeId); } @Override public int hashCode() { - return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, employeeId); + return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petId, employeeId); } @Override @@ -122,7 +121,7 @@ public class AppointmentRequest { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", petIds=" + petIds + + ", petId=" + petId + ", employeeId=" + employeeId + '}'; } 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 f8e14ac2..655a788c 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 @@ -3,7 +3,6 @@ package com.petshop.backend.dto.appointment; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.List; import java.util.Objects; public class AppointmentResponse { @@ -19,15 +18,15 @@ public class AppointmentResponse { private String appointmentStatus; private Long employeeId; private String employeeName; - private List petNames; - private List petIds; + private String petName; + private Long petId; private LocalDateTime createdAt; private LocalDateTime updatedAt; public AppointmentResponse() { } - public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { + public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, String petName, Long petId, LocalDateTime createdAt, LocalDateTime updatedAt) { this.appointmentId = appointmentId; this.customerId = customerId; this.customerName = customerName; @@ -38,8 +37,8 @@ public class AppointmentResponse { this.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; this.appointmentStatus = appointmentStatus; - this.petNames = petNames; - this.petIds = petIds; + this.petName = petName; + this.petId = petId; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -140,20 +139,20 @@ public class AppointmentResponse { this.employeeName = employeeName; } - public List getPetNames() { - return petNames; + public String getPetName() { + return petName; } - public void setPetNames(List petNames) { - this.petNames = petNames; + public void setPetName(String petName) { + this.petName = petName; } - public List getPetIds() { - return petIds; + public Long getPetId() { + return petId; } - public void setPetIds(List petIds) { - this.petIds = petIds; + public void setPetId(Long petId) { + this.petId = petId; } public LocalDateTime getCreatedAt() { @@ -177,12 +176,12 @@ public class AppointmentResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AppointmentResponse that = (AppointmentResponse) o; - return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petName, that.petName) && Objects.equals(petId, that.petId) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); + return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petName, petId, createdAt, updatedAt); } @Override @@ -198,8 +197,8 @@ public class AppointmentResponse { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", petNames=" + petNames + - ", petIds=" + petIds + + ", petName='" + petName + '\'' + + ", petId=" + petId + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; 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 f313d928..0e80f58e 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/backend/src/main/java/com/petshop/backend/entity/Appointment.java @@ -7,9 +7,7 @@ import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; @Entity @Table(name = "appointment") @@ -44,13 +42,9 @@ public class Appointment { @Column(nullable = false, length = 20) private String appointmentStatus; - @ManyToMany - @JoinTable( - name = "appointmentPet", - joinColumns = @JoinColumn(name = "appointmentId"), - inverseJoinColumns = @JoinColumn(name = "petId") - ) - private Set pets = new HashSet<>(); + @ManyToOne + @JoinColumn(name = "petId") + private Pet pet; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -127,12 +121,12 @@ public class Appointment { this.appointmentStatus = appointmentStatus; } - public Set getPets() { - return pets; + public Pet getPet() { + return pet; } - public void setPets(Set pets) { - this.pets = pets; + public void setPet(Pet pet) { + this.pet = pet; } public LocalDateTime getCreatedAt() { @@ -175,7 +169,7 @@ public class Appointment { ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + ", appointmentStatus='" + appointmentStatus + '\'' + - ", pets=" + pets + + ", pet=" + pet + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 98aac596..5c79d7a6 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: none + ddl-auto: validate naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} @@ -32,7 +32,7 @@ spring: flyway: enabled: true baseline-on-migrate: true - baseline-version: 0 + baseline-version: 1 server: port: ${SERVER_PORT:8080} diff --git a/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java b/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java deleted file mode 100644 index e7caa86e..00000000 --- a/backend/src/test/java/com/petshop/backend/controller/DropdownControllerTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.petshop.backend.controller; - -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.StoreLocation; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.CategoryRepository; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.ProductRepository; -import com.petshop.backend.repository.ServiceRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.repository.SupplierRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class DropdownControllerTest { - - private PetRepository petRepository; - private CustomerRepository customerRepository; - private CustomerPetRepository customerPetRepository; - private ServiceRepository serviceRepository; - private ProductRepository productRepository; - private CategoryRepository categoryRepository; - private StoreRepository storeRepository; - private SupplierRepository supplierRepository; - private EmployeeStoreRepository employeeStoreRepository; - private UserRepository userRepository; - private DropdownController controller; - - @BeforeEach - void setUp() { - petRepository = mock(PetRepository.class); - customerRepository = mock(CustomerRepository.class); - customerPetRepository = mock(CustomerPetRepository.class); - serviceRepository = mock(ServiceRepository.class); - productRepository = mock(ProductRepository.class); - categoryRepository = mock(CategoryRepository.class); - storeRepository = mock(StoreRepository.class); - supplierRepository = mock(SupplierRepository.class); - employeeStoreRepository = mock(EmployeeStoreRepository.class); - userRepository = mock(UserRepository.class); - - controller = new DropdownController( - petRepository, - customerRepository, - customerPetRepository, - serviceRepository, - productRepository, - categoryRepository, - storeRepository, - supplierRepository, - employeeStoreRepository, - userRepository - ); - } - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - null, - List.of() - ) - ); - } - - @Test - void getStoreEmployeesReturnsOnlyStaffLinkedEmployees() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - Employee adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - - User adminUser = new User(); - adminUser.setId(8L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(staffEmployee, store), new EmployeeStore(adminEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser)); - - var response = controller.getStoreEmployees(1L); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(7L), response.getBody().get(0).getId()); - } - - @Test - void getStoreEmployeesReturnsAllStaffWhenStoreIdIsNull() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - - when(employeeStoreRepository.findActiveAllOrderByEmployeeEmployeeIdAsc()) - .thenReturn(List.of(new EmployeeStore(staffEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - var response = controller.getStoreEmployees(null); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(7L), response.getBody().get(0).getId()); - } - - @Test - void getStoreEmployeesExcludesInactiveStaffUsers() { - StoreLocation store = new StoreLocation(); - store.setStoreId(1L); - - Employee inactiveStaffEmployee = new Employee(); - inactiveStaffEmployee.setEmployeeId(7L); - inactiveStaffEmployee.setUserId(7L); - inactiveStaffEmployee.setFirstName("Alex"); - inactiveStaffEmployee.setLastName("Jones"); - inactiveStaffEmployee.setIsActive(true); - - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(inactiveStaffEmployee, store))); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - var response = controller.getStoreEmployees(1L); - - assertEquals(0, response.getBody().size()); - } - - @Test - void getAppointmentCustomersReturnsOnlyCustomersWithPetsForStaff() { - User staffUser = new User(); - staffUser.setId(99L); - staffUser.setRole(User.Role.STAFF); - when(userRepository.findById(99L)).thenReturn(Optional.of(staffUser)); - setAuthentication(99L, User.Role.STAFF); - - Customer one = new Customer(); - one.setCustomerId(1L); - one.setFirstName("Alex"); - one.setLastName("Brown"); - - when(customerRepository.findAllWithPets()).thenReturn(List.of(one)); - - var response = controller.getAppointmentCustomers(); - - assertEquals(1, response.getBody().size()); - assertEquals(Long.valueOf(1L), response.getBody().get(0).getId()); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java b/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java deleted file mode 100644 index a133c29f..00000000 --- a/backend/src/test/java/com/petshop/backend/service/AdoptionServiceTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.adoption.AdoptionRequest; -import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.quality.Strictness; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class AdoptionServiceTest { - - @Mock private AdoptionRepository adoptionRepository; - @Mock private PetRepository petRepository; - @Mock private CustomerRepository customerRepository; - @Mock private EmployeeRepository employeeRepository; - @Mock private UserRepository userRepository; - - @InjectMocks - private AdoptionService adoptionService; - - private Pet pet; - private Customer customer; - private Employee staffEmployee; - private Employee adminEmployee; - - @BeforeEach - void setUp() { - pet = new Pet(); - pet.setPetId(1L); - pet.setPetName("Buddy"); - pet.setPetStatus("Available"); - - customer = new Customer(); - customer.setCustomerId(1L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - - staffEmployee = new Employee(); - staffEmployee.setEmployeeId(7L); - staffEmployee.setUserId(7L); - staffEmployee.setFirstName("Alex"); - staffEmployee.setLastName("Jones"); - staffEmployee.setIsActive(true); - - adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - User adminUser = new User(); - adminUser.setId(8L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser)); - } - - @Test - void createAdoptionAutoAssignsFirstStaffEmployee() { - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - - when(employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc()).thenReturn(List.of(adminEmployee, staffEmployee)); - when(adoptionRepository.save(any(Adoption.class))).thenAnswer(invocation -> { - Adoption adoption = invocation.getArgument(0); - adoption.setAdoptionId(10L); - return adoption; - }); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - var response = adoptionService.createAdoption(request); - - assertEquals(7L, response.getEmployeeId()); - assertEquals("Alex Jones", response.getEmployeeName()); - } - - @Test - void createAdoptionRejectsAdminEmployeeSelection() { - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee)); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setEmployeeId(8L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request)); - } - - @Test - void createAdoptionRejectsInactiveStaffUserSelection() { - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(employeeRepository.findById(7L)).thenReturn(Optional.of(staffEmployee)); - - AdoptionRequest request = new AdoptionRequest(); - request.setPetId(1L); - request.setCustomerId(1L); - request.setEmployeeId(7L); - request.setAdoptionDate(LocalDate.now()); - request.setAdoptionStatus("Pending"); - - assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request)); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java b/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java deleted file mode 100644 index 3e6f2d89..00000000 --- a/backend/src/test/java/com/petshop/backend/service/AppointmentServiceTest.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Appointment; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.CustomerPet; -import com.petshop.backend.entity.Employee; -import com.petshop.backend.entity.EmployeeStore; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.Service; -import com.petshop.backend.entity.StoreLocation; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.AppointmentRepository; -import com.petshop.backend.repository.CustomerPetRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.EmployeeRepository; -import com.petshop.backend.repository.EmployeeStoreRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.ServiceRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.repository.UserRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.quality.Strictness; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class AppointmentServiceTest { - - @Mock private AppointmentRepository appointmentRepository; - @Mock private CustomerRepository customerRepository; - @Mock private CustomerPetRepository customerPetRepository; - @Mock private ServiceRepository serviceRepository; - @Mock private PetRepository petRepository; - @Mock private StoreRepository storeRepository; - @Mock private UserRepository userRepository; - @Mock private EmployeeRepository employeeRepository; - @Mock private EmployeeStoreRepository employeeStoreRepository; - - @InjectMocks - private AppointmentService appointmentService; - - private Customer customer; - private StoreLocation store; - private Service grooming; - private Service nailTrim; - private Pet pet; - private CustomerPet customerPet; - private Employee employee; - private LocalDate date; - - @BeforeEach - void setUp() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - customer = new Customer(); - customer.setCustomerId(1L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - - store = new StoreLocation(); - store.setStoreId(1L); - store.setStoreName("Main Store"); - - grooming = new Service(); - grooming.setServiceId(1L); - grooming.setServiceName("Grooming"); - grooming.setServiceDuration(30); - - nailTrim = new Service(); - nailTrim.setServiceId(2L); - nailTrim.setServiceName("Nail Trim"); - nailTrim.setServiceDuration(30); - - pet = new Pet(); - pet.setPetId(1L); - pet.setPetName("Milo"); - - customerPet = new CustomerPet(); - customerPet.setCustomerPetId(11L); - customerPet.setPetName("Milo Jr"); - customerPet.setCustomer(customer); - - employee = new Employee(); - employee.setEmployeeId(7L); - employee.setUserId(7L); - employee.setFirstName("Alex"); - employee.setLastName("Jones"); - employee.setIsActive(true); - - User staffUser = new User(); - staffUser.setId(7L); - staffUser.setRole(User.Role.STAFF); - staffUser.setActive(true); - when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser)); - - date = LocalDate.now().plusDays(1); - } - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - void checkAvailabilityAllowsConcurrentAppointmentsIfAnotherEmployeeFree() { - Employee employee2 = new Employee(); - employee2.setEmployeeId(8L); - employee2.setUserId(8L); - employee2.setFirstName("Bob"); - employee2.setIsActive(true); - - User staffUser2 = new User(); - staffUser2.setId(8L); - staffUser2.setRole(User.Role.STAFF); - staffUser2.setActive(true); - when(userRepository.findById(8L)).thenReturn(Optional.of(staffUser2)); - - Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(2L)).thenReturn(Optional.of(nailTrim)); - - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store), new EmployeeStore(employee2, store))); - - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of(existing)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(8L, date)).thenReturn(List.of()); - - List slots = appointmentService.checkAvailability(1L, 2L, date); - - assertTrue(slots.contains("10:00")); - } - - @Test - void createAppointmentRejectsCustomerPetOwnedByDifferentCustomerForStaff() { - setAuthentication(7L, User.Role.STAFF); - when(employeeRepository.findByUserId(7L)).thenReturn(Optional.of(employee)); - when(employeeStoreRepository.findByEmployeeEmployeeId(7L)).thenReturn(Optional.of(new EmployeeStore(employee, store))); - - Customer otherCustomer = new Customer(); - otherCustomer.setCustomerId(2L); - - CustomerPet otherCustomerPet = new CustomerPet(); - otherCustomerPet.setCustomerPetId(22L); - otherCustomerPet.setCustomer(otherCustomer); - otherCustomerPet.setPetName("Not Yours"); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet)); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(22L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsCustomerPetOwnedByDifferentCustomer() { - setAuthentication(99L, User.Role.ADMIN); - - Customer otherCustomer = new Customer(); - otherCustomer.setCustomerId(2L); - - CustomerPet otherCustomerPet = new CustomerPet(); - otherCustomerPet.setCustomerPetId(22L); - otherCustomerPet.setCustomer(otherCustomer); - otherCustomerPet.setPetName("Not Yours"); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet)); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(22L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsAdminEmployeeSelection() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - Employee adminEmployee = new Employee(); - adminEmployee.setEmployeeId(8L); - adminEmployee.setUserId(8L); - adminEmployee.setFirstName("Admin"); - adminEmployee.setLastName("Helper"); - adminEmployee.setIsActive(true); - - User adminLinkedUser = new User(); - adminLinkedUser.setId(8L); - adminLinkedUser.setRole(User.Role.ADMIN); - adminLinkedUser.setActive(true); - - when(userRepository.findById(8L)).thenReturn(Optional.of(adminLinkedUser)); - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(adminEmployee, store), new EmployeeStore(employee, store))); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setEmployeeId(8L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(11L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - @Test - void createAppointmentRejectsInactiveStaffUserSelection() { - setAuthentication(99L, User.Role.ADMIN); - User adminUser = new User(); - adminUser.setId(99L); - adminUser.setRole(User.Role.ADMIN); - adminUser.setActive(true); - when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser)); - - User inactiveStaffUser = new User(); - inactiveStaffUser.setId(7L); - inactiveStaffUser.setRole(User.Role.STAFF); - inactiveStaffUser.setActive(false); - when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser)); - - when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); - when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); - when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); - when(employeeRepository.findById(7L)).thenReturn(Optional.of(employee)); - when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of()); - when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet)); - when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L)) - .thenReturn(List.of(new EmployeeStore(employee, store))); - - var request = new com.petshop.backend.dto.appointment.AppointmentRequest(); - request.setCustomerId(1L); - request.setStoreId(1L); - request.setServiceId(1L); - request.setEmployeeId(7L); - request.setAppointmentDate(date); - request.setAppointmentTime(LocalTime.of(10, 0)); - request.setAppointmentStatus("Booked"); - request.setCustomerPetIds(List.of(11L)); - - assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request)); - } - - private Appointment appointment(Long id, LocalDate date, LocalTime time, Service service, StoreLocation storeLocation) { - Appointment appointment = new Appointment(); - appointment.setAppointmentId(id); - appointment.setAppointmentDate(date); - appointment.setAppointmentTime(time); - appointment.setAppointmentStatus("Booked"); - appointment.setService(service); - appointment.setStore(storeLocation); - appointment.setEmployee(employee); - appointment.setCustomer(customer); - appointment.setPets(Set.of()); - appointment.setCustomerPets(Set.of()); - return appointment; - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - "n/a", - List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) - ) - ); - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java b/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java deleted file mode 100644 index 4ce13bd2..00000000 --- a/backend/src/test/java/com/petshop/backend/service/ChatServiceTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.chat.MessageRequest; -import com.petshop.backend.dto.chat.UpdateConversationRequest; -import com.petshop.backend.entity.Conversation; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Message; -import com.petshop.backend.entity.User; -import com.petshop.backend.repository.ConversationRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.MessageRepository; -import com.petshop.backend.repository.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.access.AccessDeniedException; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ChatServiceTest { - - @Mock - private ConversationRepository conversationRepository; - - @Mock - private MessageRepository messageRepository; - - @Mock - private UserRepository userRepository; - - @Mock - private CustomerRepository customerRepository; - - @InjectMocks - private ChatService chatService; - - private Customer customer; - - @BeforeEach - void setUp() { - customer = new Customer(); - customer.setCustomerId(1L); - customer.setUserId(10L); - customer.setFirstName("Pat"); - customer.setLastName("Owner"); - customer.setEmail("pat@example.com"); - } - - @Test - void updateConversationMarksConversationClosed() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)) - .thenReturn(List.of(message("hello"))); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - assertEquals("hello", response.getLastMessage()); - verify(conversationRepository).save(conversation); - } - - @Test - void updateConversationRejectsOtherCustomer() { - Conversation conversation = conversation(99L, 2L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - - assertThrows(AccessDeniedException.class, - () -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED"))); - } - - @Test - void updateConversationIsIdempotent() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void staffCanCloseAssignedConversation() { - Conversation conversation = conversation(99L, 1L, 77L, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void staffCanCloseUnassignedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void adminCanCloseAnyConversation() { - Conversation conversation = conversation(99L, 2L, 88L, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 1L, User.Role.ADMIN, new UpdateConversationRequest("CLOSED")); - - assertEquals("CLOSED", response.getStatus()); - } - - @Test - void updateConversationCanReopenClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of()); - - var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("OPEN")); - - assertEquals("OPEN", response.getStatus()); - } - - @Test - void updateConversationRejectsInvalidStatus() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer)); - - assertThrows(IllegalArgumentException.class, - () -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("INVALID"))); - } - - @Test - void sendMessageRejectsClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - - assertThrows(AccessDeniedException.class, - () -> chatService.sendMessage(99L, 10L, User.Role.CUSTOMER, new MessageRequest("hello"))); - - verify(messageRepository, never()).save(any()); - } - - @Test - void requestHumanTakeoverRejectsClosedConversation() { - Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED); - when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation)); - - assertThrows(AccessDeniedException.class, - () -> chatService.requestHumanTakeover(99L, 10L, User.Role.CUSTOMER)); - } - - private Conversation conversation(Long id, Long customerId, Long staffId, Conversation.ConversationStatus status) { - Conversation conversation = new Conversation(); - conversation.setId(id); - conversation.setCustomerId(customerId); - conversation.setStaffId(staffId); - conversation.setStatus(status); - conversation.setMode(Conversation.ConversationMode.AUTOMATED); - conversation.setHumanRequestedAt(LocalDateTime.now()); - return conversation; - } - - private Message message(String content) { - Message message = new Message(); - message.setConversationId(99L); - message.setSenderId(10L); - message.setContent(content); - message.setIsRead(false); - return message; - } -} diff --git a/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java b/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java deleted file mode 100644 index 6a80e9ca..00000000 --- a/backend/src/test/java/com/petshop/backend/service/PetServiceTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.entity.Adoption; -import com.petshop.backend.entity.Customer; -import com.petshop.backend.entity.Pet; -import com.petshop.backend.entity.User; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.AdoptionRepository; -import com.petshop.backend.repository.CustomerRepository; -import com.petshop.backend.repository.PetRepository; -import com.petshop.backend.repository.StoreRepository; -import com.petshop.backend.security.AppPrincipal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PetServiceTest { - - @Mock - private PetRepository petRepository; - - @Mock - private AdoptionRepository adoptionRepository; - - @Mock - private CustomerRepository customerRepository; - - @Mock - private StoreRepository storeRepository; - - @Mock - private CatalogImageStorageService catalogImageStorageService; - - @InjectMocks - private PetService petService; - - @AfterEach - void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - void getAllPetsAnonymousReturnsOnlyPublicPets() { - Pageable pageable = PageRequest.of(0, 10); - Pet availablePet = pet(1L, "Buddy", "Available"); - when(petRepository.searchPublicPets(null, null, null, pageable)).thenReturn(new PageImpl<>(List.of(availablePet), pageable, 1)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(1, result.getTotalElements()); - assertEquals("Buddy", result.getContent().get(0).getPetName()); - verify(petRepository).searchPublicPets(null, null, null, pageable); - verify(petRepository, never()).searchPets(null, null, null, null, pageable); - } - - @Test - void getAllPetsAnonymousWithAdoptedStatusReturnsEmptyPage() { - Pageable pageable = PageRequest.of(0, 10); - - var result = petService.getAllPets(null, null, "Adopted", null, pageable); - - assertEquals(0, result.getTotalElements()); - verify(petRepository, never()).searchPublicPets(null, null, null, pageable); - } - - @Test - void getAllPetsCustomerReturnsVisiblePetsOnly() { - Pageable pageable = PageRequest.of(0, 10); - setAuthentication(25L, User.Role.CUSTOMER); - Pet availablePet = pet(1L, "Buddy", "Available"); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.searchCustomerVisiblePets(25L, null, null, null, pageable)) - .thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(2, result.getTotalElements()); - verify(petRepository).searchCustomerVisiblePets(25L, null, null, null, pageable); - } - - @Test - void getAllPetsAdminReturnsAllPets() { - Pageable pageable = PageRequest.of(0, 10); - setAuthentication(99L, User.Role.ADMIN); - Pet availablePet = pet(1L, "Buddy", "Available"); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.searchPets(null, null, null, null, pageable)) - .thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2)); - - var result = petService.getAllPets(null, null, null, null, pageable); - - assertEquals(2, result.getTotalElements()); - verify(petRepository).searchPets(null, null, null, null, pageable); - } - - @Test - void getPetByIdHidesAdoptedPetFromUnrelatedCustomer() { - setAuthentication(50L, User.Role.CUSTOMER); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet)); - when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed")) - .thenReturn(Optional.of(adoption(2L, 25L))); - - assertThrows(ResourceNotFoundException.class, () -> petService.getPetById(2L)); - } - - @Test - void getPetByIdAllowsOwnerToSeeAdoptedPet() { - setAuthentication(25L, User.Role.CUSTOMER); - Pet adoptedPet = pet(2L, "Luna", "Adopted"); - when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet)); - when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed")) - .thenReturn(Optional.of(adoption(2L, 25L))); - - var result = petService.getPetById(2L); - - assertEquals(2L, result.getPetId()); - } - - private void setAuthentication(Long userId, User.Role role) { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken( - new AppPrincipal(userId, "user", role, 0), - "n/a", - List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) - ) - ); - } - - private Pet pet(Long id, String name, String status) { - Pet pet = new Pet(); - pet.setPetId(id); - pet.setPetName(name); - pet.setPetSpecies("Cat"); - pet.setPetBreed("Mixed"); - pet.setPetAge(2); - pet.setPetStatus(status); - pet.setPetPrice(java.math.BigDecimal.TEN); - return pet; - } - - private Adoption adoption(Long petId, Long userId) { - Adoption adoption = new Adoption(); - Pet pet = new Pet(); - pet.setPetId(petId); - adoption.setPet(pet); - Customer customer = new Customer(); - customer.setCustomerId(1L); - customer.setUserId(userId); - adoption.setCustomer(customer); - adoption.setAdoptionStatus("Completed"); - return adoption; - } -} -- 2.49.1