From fad80e436f93d95742db06296cd292b6f6cf3400 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 16:43:46 -0600 Subject: [PATCH] Backend correctness fixes --- Petstoredata.sql | 394 ---------------- sql/01_petstore_init.sql | 436 ------------------ .../backend/controller/AuthController.java | 5 + .../backend/controller/ChatController.java | 2 +- .../controller/DropdownController.java | 1 + .../dto/appointment/AppointmentRequest.java | 15 +- .../dto/appointment/AppointmentResponse.java | 28 +- .../petshop/backend/entity/Appointment.java | 16 +- .../petshop/backend/entity/Conversation.java | 2 +- .../com/petshop/backend/entity/Refund.java | 2 +- .../repository/AppointmentRepository.java | 4 +- .../repository/EmployeeStoreRepository.java | 12 + .../backend/repository/SaleRepository.java | 4 + .../security/JwtAuthenticationFilter.java | 14 +- .../backend/service/AppointmentService.java | 87 +++- .../petshop/backend/service/ChatService.java | 16 + .../petshop/backend/service/SaleService.java | 26 +- src/main/resources/application.yml | 2 +- src/main/resources/data.sql | 205 -------- ...t_store_and_employee_store_constraints.sql | 19 + src/main/resources/schema.sql | 250 ---------- 21 files changed, 235 insertions(+), 1305 deletions(-) delete mode 100644 Petstoredata.sql delete mode 100644 sql/01_petstore_init.sql create mode 100644 src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java delete mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql delete mode 100644 src/main/resources/schema.sql diff --git a/Petstoredata.sql b/Petstoredata.sql deleted file mode 100644 index 8c7c1576..00000000 --- a/Petstoredata.sql +++ /dev/null @@ -1,394 +0,0 @@ -DROP DATABASE IF EXISTS Petstoredb; -CREATE DATABASE Petstoredb; -USE Petstoredb; - --- Create Tables - -CREATE TABLE storeLocation ( - storeId INT AUTO_INCREMENT PRIMARY KEY, - storeName VARCHAR(100) NOT NULL, - address VARCHAR(255) NOT NULL, - phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL -); - -CREATE TABLE employee ( - employeeId INT AUTO_INCREMENT PRIMARY KEY, - 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 -); - -CREATE TABLE employeeStore ( - employeeId INT NOT NULL, - storeId INT NOT NULL, - PRIMARY KEY (employeeId, storeId), - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) -); - -CREATE TABLE customer ( - customerId INT AUTO_INCREMENT PRIMARY KEY, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL -); - -CREATE TABLE pet ( - petId INT 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 -); - -CREATE TABLE adoption ( - adoptionId INT AUTO_INCREMENT PRIMARY KEY, - petId INT NOT NULL, - customerId INT NOT NULL, - adoptionDate DATE NOT NULL, - adoptionStatus VARCHAR(20) NOT NULL, - FOREIGN KEY (petId) REFERENCES pet(petId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE supplier ( - supId INT 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 -); - -CREATE TABLE category ( - categoryId INT AUTO_INCREMENT PRIMARY KEY, - categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL -); - -CREATE TABLE product ( - prodId INT AUTO_INCREMENT PRIMARY KEY, - prodName VARCHAR(100) NOT NULL, - prodPrice DECIMAL(10, 2) NOT NULL, - categoryId INT NOT NULL, - prodDesc TEXT, - FOREIGN KEY (categoryId) REFERENCES category(categoryId) -); - -CREATE TABLE productSupplier ( - supId INT NOT NULL, - prodId INT NOT NULL, - cost DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (supId, prodId), - FOREIGN KEY (supId) REFERENCES supplier(supId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE inventory ( - inventoryId INT AUTO_INCREMENT PRIMARY KEY, - prodId INT NOT NULL, - quantity INT DEFAULT 0 NOT NULL, - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE service ( - serviceId INT AUTO_INCREMENT PRIMARY KEY, - serviceName VARCHAR(100) NOT NULL, - serviceDesc TEXT, - serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL -); - -CREATE TABLE appointment ( - appointmentId INT AUTO_INCREMENT PRIMARY KEY, - serviceId INT NOT NULL, - customerId INT NOT NULL, - appointmentDate DATE NOT NULL, - appointmentTime TIME NOT NULL, - appointmentStatus VARCHAR(20) NOT NULL, - FOREIGN KEY (serviceId) REFERENCES service(serviceId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE appointmentPet ( - appointmentId INT NOT NULL, - petId INT NOT NULL, - PRIMARY KEY (appointmentId, petId), - FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), - FOREIGN KEY (petId) REFERENCES pet(petId) -); - -CREATE TABLE sale ( - saleId INT AUTO_INCREMENT PRIMARY KEY, - saleDate DATETIME NOT NULL, - totalAmount DECIMAL(10, 2) NOT NULL, - paymentMethod VARCHAR(50) NOT NULL, - employeeId INT NOT NULL, - storeId INT NOT NULL, - isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId INT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE saleItem ( - saleItemId INT AUTO_INCREMENT PRIMARY KEY, - saleId INT NOT NULL, - prodId INT NOT NULL, - quantity INT NOT NULL, - unitPrice DECIMAL(10, 2) NOT NULL, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE purchaseOrder ( - purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, - supId INT NOT NULL, - orderDate DATE NOT NULL, - status VARCHAR(50) NOT NULL, - FOREIGN KEY (supId) REFERENCES supplier(supId) -); - -CREATE TABLE activityLog ( - logId INT AUTO_INCREMENT PRIMARY KEY, - employeeId INT NOT NULL, - activity TEXT NOT NULL, - logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId) -); - --- 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) -VALUES --- January Sales -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys --- February Sales -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats -(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES --- Sale 1 items (Dog food + treats) -(1, 1, 2, 50.00), -- 2x Premium Dog Food -(1, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 2 items (Bird cage + fish tank) -(2, 3, 1, 120.00), -- 1x Bird Cage Large -(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 3 items (Cat toys + hamster wheel) -(3, 2, 3, 10.00), -- 3x Cat Toy Ball -(3, 5, 2, 15.00), -- 2x Hamster Wheel --- Sale 4 items (Dog food bulk) -(4, 1, 3, 50.00), -- 3x Premium Dog Food --- Sale 5 items (Fish tank) -(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 6 items (Mixed) -(6, 2, 4, 10.00), -- 4x Cat Toy Ball -(6, 5, 1, 15.00), -- 1x Hamster Wheel -(6, 6, 1, 25.00), -- 1x Organic Dog Treats -(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) --- Sale 7 items (Two bird cages) -(7, 3, 2, 120.00), -- 2x Bird Cage Large --- Sale 8 items (Dog food + cat toys) -(8, 1, 1, 50.00), -- 1x Premium Dog Food -(8, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 9 items (Dog food + treats bulk) -(9, 1, 3, 50.00), -- 3x Premium Dog Food -(9, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 10 items (Bird cage) -(10, 3, 1, 120.00), -- 1x Bird Cage Large --- Sale 11 items (Hamster wheel + cat toys) -(11, 5, 1, 15.00), -- 1x Hamster Wheel -(11, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 12 items (Fish tank + accessories) -(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon --- Sale 13 items (Dog treats bulk) -(13, 6, 4, 25.00), -- 4x Organic Dog Treats --- Sale 14 items (Dog food) -(14, 1, 1, 50.00), -- 1x Premium Dog Food --- Sale 15 items (Mixed supplies) -(15, 2, 2, 10.00), -- 2x Cat Toy Ball -(15, 5, 1, 15.00), -- 1x Hamster Wheel -(15, 6, 2, 25.00), -- 2x Organic Dog Treats --- Sale 16 items (Bird cage + hamster wheel) -(16, 3, 1, 120.00), -- 1x Bird Cage Large -(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 17 items (Fish tank + cat toys) -(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon -(17, 1, 1, 50.00), -- 1x Premium Dog Food -(17, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 18 items (Dog treats + toys) -(18, 6, 2, 25.00), -- 2x Organic Dog Treats -(18, 2, 2, 10.00), -- 2x Cat Toy Ball -(18, 5, 1, 15.00), -- 1x Hamster Wheel --- Sale 19 items (Dog food + treats) -(19, 1, 2, 50.00), -- 2x Premium Dog Food -(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) --- Sale 20 items (Recent sale) -(20, 2, 5, 10.00), -- 5x Cat Toy Ball -(20, 5, 3, 15.00); -- 3x Hamster Wheel - -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'); \ No newline at end of file diff --git a/sql/01_petstore_init.sql b/sql/01_petstore_init.sql deleted file mode 100644 index c8e8dbea..00000000 --- a/sql/01_petstore_init.sql +++ /dev/null @@ -1,436 +0,0 @@ -DROP DATABASE IF EXISTS Petstoredb; -CREATE DATABASE Petstoredb; -USE Petstoredb; - --- Create Tables - -CREATE TABLE 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 employee ( - employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, - 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 -); - -CREATE TABLE 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 customer ( - customerId BIGINT AUTO_INCREMENT PRIMARY KEY, - 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 -); - -CREATE TABLE 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 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 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 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 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 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 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 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 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 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 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, - 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 (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE 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 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 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) -); - --- Backend-only table for API authentication -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- 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) -VALUES --- January Sales -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys --- February Sales -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats -(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES --- Sale 1 items (Dog food + treats) -(1, 1, 2, 50.00), -- 2x Premium Dog Food -(1, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 2 items (Bird cage + fish tank) -(2, 3, 1, 120.00), -- 1x Bird Cage Large -(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 3 items (Cat toys + hamster wheel) -(3, 2, 3, 10.00), -- 3x Cat Toy Ball -(3, 5, 2, 15.00), -- 2x Hamster Wheel --- Sale 4 items (Dog food bulk) -(4, 1, 3, 50.00), -- 3x Premium Dog Food --- Sale 5 items (Fish tank) -(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 6 items (Mixed) -(6, 2, 4, 10.00), -- 4x Cat Toy Ball -(6, 5, 1, 15.00), -- 1x Hamster Wheel -(6, 6, 1, 25.00), -- 1x Organic Dog Treats -(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) --- Sale 7 items (Two bird cages) -(7, 3, 2, 120.00), -- 2x Bird Cage Large --- Sale 8 items (Dog food + cat toys) -(8, 1, 1, 50.00), -- 1x Premium Dog Food -(8, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 9 items (Dog food + treats bulk) -(9, 1, 3, 50.00), -- 3x Premium Dog Food -(9, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 10 items (Bird cage) -(10, 3, 1, 120.00), -- 1x Bird Cage Large --- Sale 11 items (Hamster wheel + cat toys) -(11, 5, 1, 15.00), -- 1x Hamster Wheel -(11, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 12 items (Fish tank + accessories) -(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon --- Sale 13 items (Dog treats bulk) -(13, 6, 4, 25.00), -- 4x Organic Dog Treats --- Sale 14 items (Dog food) -(14, 1, 1, 50.00), -- 1x Premium Dog Food --- Sale 15 items (Mixed supplies) -(15, 2, 2, 10.00), -- 2x Cat Toy Ball -(15, 5, 1, 15.00), -- 1x Hamster Wheel -(15, 6, 2, 25.00), -- 2x Organic Dog Treats --- Sale 16 items (Bird cage + hamster wheel) -(16, 3, 1, 120.00), -- 1x Bird Cage Large -(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 17 items (Fish tank + cat toys) -(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon -(17, 1, 1, 50.00), -- 1x Premium Dog Food -(17, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 18 items (Dog treats + toys) -(18, 6, 2, 25.00), -- 2x Organic Dog Treats -(18, 2, 2, 10.00), -- 2x Cat Toy Ball -(18, 5, 1, 15.00), -- 1x Hamster Wheel --- Sale 19 items (Dog food + treats) -(19, 1, 2, 50.00), -- 2x Premium Dog Food -(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) --- Sale 20 items (Recent sale) -(20, 2, 5, 10.00), -- 5x Cat Toy Ball -(20, 5, 3, 15.00); -- 3x Hamster Wheel - -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'); - --- Sample users created by DataInitializer (admin/admin123, staff/staff123) diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 22f53420..7aa04307 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -16,6 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -125,6 +126,10 @@ public class AuthController { Map error = new HashMap<>(); error.put("message", "Invalid username or password"); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } catch (DisabledException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); } } diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index f503cedf..5ffb1444 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -40,7 +40,7 @@ public class ChatController { } @PostMapping("/conversations") - @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + @PreAuthorize("hasRole('CUSTOMER')") public ResponseEntity createConversation(@Valid @RequestBody ConversationRequest request) { User user = getCurrentUser(); ConversationResponse response = chatService.createConversation(user.getId(), request); diff --git a/src/main/java/com/petshop/backend/controller/DropdownController.java b/src/main/java/com/petshop/backend/controller/DropdownController.java index bf1d1c48..16763b8a 100644 --- a/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -46,6 +46,7 @@ public class DropdownController { } @GetMapping("/customers") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getCustomers() { return ResponseEntity.ok( customerRepository.findAll().stream() diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index bd2b2356..247e5ae4 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -11,6 +11,9 @@ public class AppointmentRequest { @NotNull(message = "Customer ID is required") private Long customerId; + @NotNull(message = "Store ID is required") + private Long storeId; + @NotNull(message = "Service ID is required") private Long serviceId; @@ -34,6 +37,14 @@ public class AppointmentRequest { this.customerId = customerId; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + public Long getServiceId() { return serviceId; } @@ -80,6 +91,7 @@ public class AppointmentRequest { if (o == null || getClass() != o.getClass()) return false; AppointmentRequest that = (AppointmentRequest) o; return Objects.equals(customerId, that.customerId) && + Objects.equals(storeId, that.storeId) && Objects.equals(serviceId, that.serviceId) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && @@ -89,13 +101,14 @@ public class AppointmentRequest { @Override public int hashCode() { - return Objects.hash(customerId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds); + return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds); } @Override public String toString() { return "AppointmentRequest{" + "customerId=" + customerId + + ", storeId=" + storeId + ", serviceId=" + serviceId + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index a4077a87..c7d2e8d7 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -10,6 +10,8 @@ public class AppointmentResponse { private Long appointmentId; private Long customerId; private String customerName; + private Long storeId; + private String storeName; private Long serviceId; private String serviceName; private LocalDate appointmentDate; @@ -23,10 +25,12 @@ public class AppointmentResponse { public AppointmentResponse() { } - public AppointmentResponse(Long appointmentId, Long customerId, String customerName, 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, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { this.appointmentId = appointmentId; this.customerId = customerId; this.customerName = customerName; + this.storeId = storeId; + this.storeName = storeName; this.serviceId = serviceId; this.serviceName = serviceName; this.appointmentDate = appointmentDate; @@ -62,6 +66,22 @@ public class AppointmentResponse { this.customerName = customerName; } + 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 Long getServiceId() { return serviceId; } @@ -139,12 +159,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(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(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(appointmentId, customerId, customerName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); + return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); } @Override @@ -153,6 +173,8 @@ public class AppointmentResponse { "appointmentId=" + appointmentId + ", customerId=" + customerId + ", customerName='" + customerName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + ", serviceId=" + serviceId + ", serviceName='" + serviceName + '\'' + ", appointmentDate=" + appointmentDate + diff --git a/src/main/java/com/petshop/backend/entity/Appointment.java b/src/main/java/com/petshop/backend/entity/Appointment.java index c31a94ff..101ff885 100644 --- a/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/src/main/java/com/petshop/backend/entity/Appointment.java @@ -23,6 +23,10 @@ public class Appointment { @JoinColumn(name = "customerId", nullable = false) private Customer customer; + @ManyToOne + @JoinColumn(name = "storeId", nullable = false) + private StoreLocation store; + @ManyToOne @JoinColumn(name = "serviceId", nullable = false) private Service service; @@ -55,9 +59,10 @@ public class Appointment { public Appointment() { } - public Appointment(Long appointmentId, Customer customer, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, 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.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; @@ -83,6 +88,14 @@ public class Appointment { this.customer = customer; } + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public Service getService() { return service; } @@ -157,6 +170,7 @@ public class Appointment { return "Appointment{" + "appointmentId=" + appointmentId + ", customer=" + customer + + ", store=" + store + ", service=" + service + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + diff --git a/src/main/java/com/petshop/backend/entity/Conversation.java b/src/main/java/com/petshop/backend/entity/Conversation.java index 1d8008e1..0a907710 100644 --- a/src/main/java/com/petshop/backend/entity/Conversation.java +++ b/src/main/java/com/petshop/backend/entity/Conversation.java @@ -21,7 +21,7 @@ public class Conversation { private Long staffId; @Enumerated(EnumType.STRING) - @Column(length = 20, nullable = false) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)") private ConversationStatus status = ConversationStatus.OPEN; @CreationTimestamp diff --git a/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java index 8addc548..4973427e 100644 --- a/src/main/java/com/petshop/backend/entity/Refund.java +++ b/src/main/java/com/petshop/backend/entity/Refund.java @@ -29,7 +29,7 @@ public class Refund { private String reason; @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private RefundStatus status; @CreationTimestamp diff --git a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 789a8b25..5c7b6ec0 100644 --- a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -18,8 +18,8 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time") List findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time); - @Query("SELECT a FROM Appointment a WHERE a.service.serviceId = :serviceId AND a.appointmentDate = :date AND a.appointmentStatus != 'Cancelled'") - List findByServiceAndDate(@Param("serviceId") Long serviceId, @Param("date") LocalDate date); + @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.store.storeId = :storeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) <> 'cancelled'") + List findByStoreAndDate(@Param("storeId") Long storeId, @Param("date") LocalDate date); @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + diff --git a/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java b/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java new file mode 100644 index 00000000..1c847817 --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java @@ -0,0 +1,12 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.EmployeeStore; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface EmployeeStoreRepository extends JpaRepository { + Optional findByEmployeeEmployeeId(Long employeeId); +} diff --git a/src/main/java/com/petshop/backend/repository/SaleRepository.java b/src/main/java/com/petshop/backend/repository/SaleRepository.java index 56b31289..f70b3dec 100644 --- a/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -8,6 +8,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface SaleRepository extends JpaRepository { @@ -16,4 +18,6 @@ public interface SaleRepository extends JpaRepository { "LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchSales(@Param("q") String query, Pageable pageable); + + List findByOriginalSaleSaleId(Long originalSaleId); } diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index 02e967aa..d79c88f6 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; +import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.time.LocalDateTime; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -45,7 +47,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { username = jwtUtil.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UserDetails userDetails; + try { + userDetails = userDetailsService.loadUserByUsername(username); + } catch (DisabledException ex) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write( + "{\"status\":401,\"message\":\"" + ex.getMessage() + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}" + ); + return; + } if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index 6b9d3548..afc3d600 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -5,14 +5,24 @@ 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.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.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.util.AuthenticationHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +32,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -32,12 +43,20 @@ public class AppointmentService { private final CustomerRepository customerRepository; 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, ServiceRepository serviceRepository, PetRepository petRepository) { + public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) { this.appointmentRepository = appointmentRepository; this.customerRepository = customerRepository; this.serviceRepository = serviceRepository; this.petRepository = petRepository; + this.storeRepository = storeRepository; + this.userRepository = userRepository; + this.employeeRepository = employeeRepository; + this.employeeStoreRepository = employeeStoreRepository; } public Page getAllAppointments(String query, Pageable pageable, Long customerId) { @@ -78,13 +97,20 @@ public class AppointmentService { Customer customer = customerRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + StoreLocation store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + validateStoreAccess(store.getStoreId()); + validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), null); + Set pets = fetchPets(request.getPetIds()); Appointment appointment = new Appointment(); appointment.setCustomer(customer); + appointment.setStore(store); appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); @@ -105,12 +131,19 @@ public class AppointmentService { Customer customer = customerRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + StoreLocation store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + validateStoreAccess(store.getStoreId()); + validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), id); + Set pets = fetchPets(request.getPetIds()); appointment.setCustomer(customer); + appointment.setStore(store); appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); @@ -135,21 +168,22 @@ public class AppointmentService { } public List checkAvailability(Long storeId, Long serviceId, LocalDate date) { + storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId)); - List existingAppointments = appointmentRepository.findByServiceAndDate(serviceId, date); - Set bookedTimes = existingAppointments.stream() - .map(Appointment::getAppointmentTime) - .collect(Collectors.toSet()); + List existingAppointments = appointmentRepository.findByStoreAndDate(storeId, date); List availableSlots = new ArrayList<>(); LocalTime startTime = LocalTime.of(9, 0); LocalTime endTime = LocalTime.of(17, 0); + LocalTime latestStart = endTime.minusMinutes(service.getServiceDuration()); LocalTime currentTime = startTime; - while (currentTime.isBefore(endTime)) { - if (!bookedTimes.contains(currentTime)) { + while (!currentTime.isAfter(latestStart)) { + if (isSlotAvailable(existingAppointments, service, currentTime, null)) { availableSlots.add(currentTime.toString()); } currentTime = currentTime.plusMinutes(30); @@ -190,6 +224,8 @@ public class AppointmentService { appointment.getAppointmentId(), appointment.getCustomer().getCustomerId(), appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName(), + appointment.getStore().getStoreId(), + appointment.getStore().getStoreName(), appointment.getService().getServiceId(), appointment.getService().getServiceName(), appointment.getAppointmentDate(), @@ -201,4 +237,41 @@ public class AppointmentService { appointment.getUpdatedAt() ); } + + private void validateAvailability(StoreLocation store, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) { + List existingAppointments = appointmentRepository.findByStoreAndDate(store.getStoreId(), date); + if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) { + throw new IllegalArgumentException("Appointment time is not available for the selected store and service"); + } + } + + private boolean isSlotAvailable(List existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) { + LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration()); + for (Appointment existingAppointment : existingAppointments) { + if (appointmentIdToIgnore != null && appointmentIdToIgnore.equals(existingAppointment.getAppointmentId())) { + continue; + } + LocalTime existingStart = existingAppointment.getAppointmentTime(); + LocalTime existingEnd = existingStart.plusMinutes(existingAppointment.getService().getServiceDuration()); + if (requestedStart.isBefore(existingEnd) && existingStart.isBefore(requestedEnd)) { + return false; + } + } + return true; + } + + private void validateStoreAccess(Long requestedStoreId) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + if (user.getRole() != User.Role.STAFF) { + 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)) { + throw new AccessDeniedException("Staff can only manage appointments for their assigned store"); + } + } } diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index 5842f6e7..af5b6332 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -43,6 +43,10 @@ public class ChatService { User user = userRepository.findById(userId) .orElseThrow(() -> new ResourceNotFoundException("User not found")); + if (user.getRole() != User.Role.CUSTOMER) { + throw new AccessDeniedException("Only customers can start new conversations"); + } + Customer customer = customerRepository.findByUserId(userId) .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); @@ -113,6 +117,18 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + if (role == User.Role.CUSTOMER) { + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + throw new AccessDeniedException("You can only send messages to your own conversations"); + } + } else if (role == User.Role.STAFF) { + if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations"); + } + } + Message message = new Message(); message.setConversationId(conversationId); message.setSenderId(userId); diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index fab3e6da..afc9d068 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -26,15 +26,17 @@ public class SaleService { 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, UserRepository userRepository, CustomerRepository customerRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, UserRepository userRepository, CustomerRepository customerRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; this.employeeRepository = employeeRepository; + this.employeeStoreRepository = employeeStoreRepository; this.userRepository = userRepository; this.customerRepository = customerRepository; } @@ -57,11 +59,20 @@ 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(); 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"); + } + Sale sale = new Sale(); sale.setSaleDate(LocalDateTime.now()); sale.setEmployee(employee); @@ -100,6 +111,19 @@ public class SaleService { " for product: " + product.getProdName()); } + int alreadyRefundedQuantity = saleRepository.findByOriginalSaleSaleId(sale.getOriginalSale().getSaleId()).stream() + .flatMap(existingRefund -> existingRefund.getItems().stream()) + .filter(existingRefundItem -> existingRefundItem.getProduct().getProdId().equals(itemRequest.getProdId())) + .mapToInt(existingRefundItem -> Math.abs(existingRefundItem.getQuantity())) + .sum(); + + int refundableQuantity = originalItem.getQuantity() - alreadyRefundedQuantity; + if (itemRequest.getQuantity() > refundableQuantity) { + throw new BusinessException("Refund quantity " + itemRequest.getQuantity() + + " exceeds remaining refundable quantity " + refundableQuantity + + " for product: " + product.getProdName()); + } + Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f7c2ceba..6338af51 100644 --- a/src/main/resources/application.yml +++ b/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} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 5e8d3fb6..00000000 --- a/src/main/resources/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/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql b/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql new file mode 100644 index 00000000..2e65bc98 --- /dev/null +++ b/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql @@ -0,0 +1,19 @@ +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/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index 097a7328..00000000 --- a/src/main/resources/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) DEFAULT 'OPEN', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP 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 DEFAULT CURRENT_TIMESTAMP, - isRead BOOLEAN 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;