Merge pull request #33 from RecentRunner/api-migration

Api migration
This commit is contained in:
2026-03-09 16:27:30 -06:00
committed by GitHub
100 changed files with 4520 additions and 4531 deletions

1
.gitignore vendored
View File

@@ -40,3 +40,4 @@ build/
## Database related
connectionpetstore.properties
.idea/workspace.xml

View File

@@ -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');

View File

@@ -1,3 +0,0 @@
url=jdbc:mysql://127.0.0.1:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
user=petapp
password=petapppass

View File

@@ -1,18 +0,0 @@
services:
mysql:
image: mysql:8.4
container_name: petstore-mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: Petstoredb
MYSQL_USER: petapp
MYSQL_PASSWORD: petapppass
volumes:
- ./Petstoredata.sql:/docker-entrypoint-initdb.d/01-Petstoredata.sql:ro
- petstore_mysql_data:/var/lib/mysql
volumes:
petstore_mysql_data:

71
log.txt
View File

@@ -45,3 +45,74 @@ The last packet sent successfully to the server was 0 milliseconds ago. The driv
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. | Context: Establishing database connection
[2026-03-02 13:02:48] [INSERT] DB_INSERT | Table: sale | ID: Refund ID: 24 | Details: Created refund for sale ID 23 with 1 items, total: $240.00
[2026-03-07 17:50:34] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Authentication attempt for username: staff
[2026-03-07 17:50:34] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Authentication attempt for username: staff
[2026-03-07 17:55:02] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Authentication attempt for username: staff
[2026-03-07 17:55:16] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Authentication attempt for username: staff
[2026-03-07 18:11:05] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: ConnectException | Message: null | Context: Authentication attempt for username: staff
[2026-03-07 18:11:42] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: RuntimeException | Message: Authentication failed. Please log in again. | Context: Authentication attempt for username: staff
[2026-03-07 18:11:48] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:11:52] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.product.ProductResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Loading products
[2026-03-07 18:11:52] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.sale.SaleResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Loading sales
[2026-03-07 18:11:53] [ERROR] EXCEPTION | Location: AppointmentController.loadAppointments | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.appointment.AppointmentResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Loading appointments for table display
[2026-03-07 18:11:53] [ERROR] EXCEPTION | Location: ServiceController.displayServices | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.service.ServiceResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching service data for table display
[2026-03-07 18:11:54] [ERROR] EXCEPTION | Location: ServiceController.displayServices | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.service.ServiceResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching service data for table display
[2026-03-07 18:11:56] [ERROR] EXCEPTION | Location: PetController.displayPets | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.pet.PetResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching pet data for table display
[2026-03-07 18:11:56] [ERROR] EXCEPTION | Location: PetController.displayPets | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.pet.PetResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching pet data for table display
[2026-03-07 18:11:56] [ERROR] EXCEPTION | Location: AdoptionController.displayAdoptions | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.adoption.AdoptionResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching adoption data for table display
[2026-03-07 18:11:57] [ERROR] EXCEPTION | Location: ProductController.displayProduct | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.util.ArrayList<org.example.petshopdesktop.api.dto.product.ProductResponse>` from Object value (token `JsonToken.START_OBJECT`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1] | Context: Fetching product data for table display
[2026-03-07 18:11:58] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:47:48] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:48:01] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:48:05] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:48:05] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:51:14] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:51:14] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:51:28] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-07 18:51:30] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-08 10:03:43] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Loading products
[2026-03-08 10:03:43] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:03:44] [ERROR] EXCEPTION | Location: AppointmentController.loadAppointments | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 219] (through reference chain: org.example.petshopdesktop.api.dto.common.PageResponse["content"]->java.util.ArrayList[0]->org.example.petshopdesktop.api.dto.appointment.AppointmentResponse["petNames"]) | Context: Loading appointments for table display
[2026-03-08 10:03:44] [ERROR] EXCEPTION | Location: ServiceController.displayServices | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.service.ServiceResponse.getId()" is null | Context: Fetching service data for table display
[2026-03-08 10:03:45] [ERROR] EXCEPTION | Location: ServiceController.displayServices | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.service.ServiceResponse.getId()" is null | Context: Fetching service data for table display
[2026-03-08 10:03:46] [ERROR] EXCEPTION | Location: PetController.displayPets | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.pet.PetResponse.getId()" is null | Context: Fetching pet data for table display
[2026-03-08 10:03:46] [ERROR] EXCEPTION | Location: AdoptionController.displayAdoptions | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.adoption.AdoptionResponse.getId()" is null | Context: Fetching adoption data for table display
[2026-03-08 10:03:47] [ERROR] EXCEPTION | Location: AdoptionController.displayAdoptions | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.adoption.AdoptionResponse.getId()" is null | Context: Fetching adoption data for table display
[2026-03-08 10:03:47] [ERROR] EXCEPTION | Location: ProductController.displayProduct | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Fetching product data for table display
[2026-03-08 10:03:48] [ERROR] EXCEPTION | Location: InventoryController.displayInventory | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.inventory.InventoryResponse.getId()" is null | Context: Fetching inventory data for table display
[2026-03-08 10:03:48] [ERROR] EXCEPTION | Location: ProductSupplierController.displayProductSupplier | Type: NullPointerException | Message: Cannot invoke "java.math.BigDecimal.doubleValue()" because the return value of "org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse.getSupplierPrice()" is null | Context: Fetching product-supplier data for table display
[2026-03-08 10:03:49] [ERROR] EXCEPTION | Location: SupplierController.displaySupplier | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.supplier.SupplierResponse.getId()" is null | Context: Fetching supplier data for table display
[2026-03-08 10:03:50] [ERROR] EXCEPTION | Location: PurchaseOrderController.loadPurchaseOrders | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.longValue()" because the return value of "org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse.getId()" is null | Context: Loading purchase orders for table display
[2026-03-08 10:04:02] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Loading products
[2026-03-08 10:04:02] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:04:03] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:05:41] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-08 10:05:44] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-08 10:05:46] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Loading products
[2026-03-08 10:05:46] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:05:47] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Loading products
[2026-03-08 10:05:47] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:05:47] [ERROR] EXCEPTION | Location: AppointmentController.loadAppointments | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 219] (through reference chain: org.example.petshopdesktop.api.dto.common.PageResponse["content"]->java.util.ArrayList[0]->org.example.petshopdesktop.api.dto.appointment.AppointmentResponse["petNames"]) | Context: Loading appointments for table display
[2026-03-08 10:05:48] [ERROR] EXCEPTION | Location: AppointmentController.loadAppointments | Type: MismatchedInputException | Message: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`)
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 219] (through reference chain: org.example.petshopdesktop.api.dto.common.PageResponse["content"]->java.util.ArrayList[0]->org.example.petshopdesktop.api.dto.appointment.AppointmentResponse["petNames"]) | Context: Loading appointments for table display
[2026-03-08 10:12:33] [ERROR] EXCEPTION | Location: LoginController.btnLoginClicked | Type: ConnectException | Message: null | Context: Authentication attempt for username: admin
[2026-03-08 10:55:59] [ERROR] EXCEPTION | Location: AnalyticsController.loadAnalyticsData | Type: RuntimeException | Message: Access restricted. You don't have permission to perform this action. | Context: Loading analytics data
[2026-03-08 10:56:02] [ERROR] EXCEPTION | Location: SaleController.setupCreateSale | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.product.ProductResponse.getId()" is null | Context: Loading products
[2026-03-08 10:56:02] [ERROR] EXCEPTION | Location: SaleController.refreshSales | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.sale.SaleResponse.getId()" is null | Context: Loading sales
[2026-03-08 10:56:04] [ERROR] EXCEPTION | Location: ServiceController.displayServices | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.service.ServiceResponse.getId()" is null | Context: Fetching service data for table display
[2026-03-08 10:56:04] [ERROR] EXCEPTION | Location: PetController.displayPets | Type: NullPointerException | Message: Cannot invoke "java.lang.Long.intValue()" because the return value of "org.example.petshopdesktop.api.dto.pet.PetResponse.getId()" is null | Context: Fetching pet data for table display

21
pom.xml
View File

@@ -40,9 +40,24 @@
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.3.0</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.18.2</version>
</dependency>
</dependencies>

View File

@@ -2,6 +2,11 @@ module org.example.petshopdesktop {
requires javafx.controls;
requires javafx.fxml;
requires java.sql;
requires java.net.http;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.datatype.jsr310;
opens org.example.petshopdesktop.DTOs to javafx.base;
opens org.example.petshopdesktop.models to javafx.base;
@@ -10,6 +15,21 @@ module org.example.petshopdesktop {
opens org.example.petshopdesktop.controllers to javafx.fxml;
opens org.example.petshopdesktop.auth to javafx.fxml;
opens org.example.petshopdesktop.api.dto.common to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.auth to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.product to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.pet to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.service to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.supplier to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.productsupplier to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.inventory to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.appointment to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.adoption to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.sale to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.user to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.analytics to com.fasterxml.jackson.databind;
opens org.example.petshopdesktop.api.dto.purchaseorder to com.fasterxml.jackson.databind;
exports org.example.petshopdesktop;
exports org.example.petshopdesktop.controllers;
exports org.example.petshopdesktop.auth;

View File

@@ -4,21 +4,21 @@ import javafx.beans.property.*;
public class PurchaseOrderDTO {
private IntegerProperty purchaseOrderId;
private LongProperty purchaseOrderId;
private StringProperty supplierName;
private StringProperty orderDate;
private StringProperty status;
public PurchaseOrderDTO(int id, String supplierName,
public PurchaseOrderDTO(long id, String supplierName,
String orderDate, String status) {
this.purchaseOrderId = new SimpleIntegerProperty(id);
this.purchaseOrderId = new SimpleLongProperty(id);
this.supplierName = new SimpleStringProperty(supplierName);
this.orderDate = new SimpleStringProperty(orderDate);
this.status = new SimpleStringProperty(status);
}
public int getPurchaseOrderId() { return purchaseOrderId.get(); }
public long getPurchaseOrderId() { return purchaseOrderId.get(); }
public String getSupplierName() { return supplierName.get(); }
public String getOrderDate() { return orderDate.get(); }
public String getStatus() { return status.get(); }

View File

@@ -4,18 +4,12 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.UserDB;
import java.io.IOException;
public class PetShopApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
try {
UserDB.initializeTable();
} catch (Exception e) {
System.err.println("Warning: could not initialize users table: " + e.getMessage());
}
FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml"));
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("Pet Shop Manager - Login");

View File

@@ -0,0 +1,188 @@
package org.example.petshopdesktop.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.example.petshopdesktop.auth.UserSession;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class ApiClient {
private static final ApiClient INSTANCE = new ApiClient();
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private final String baseUrl;
private ApiClient() {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.objectMapper = new ObjectMapper();
this.objectMapper.registerModule(new JavaTimeModule());
this.objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.baseUrl = ApiConfig.getInstance().getBaseUrl();
}
public static ApiClient getInstance() {
return INSTANCE;
}
public <T> T get(String path, Class<T> responseClass) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.GET()
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return handleResponse(response, responseClass);
}
public String getRawResponse(String path) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.GET()
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200 || response.statusCode() == 201) {
return response.body();
} else if (response.statusCode() == 401) {
throw new RuntimeException("Authentication failed. Please log in again.");
} else if (response.statusCode() == 403) {
throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
} else {
throw new RuntimeException(parseErrorMessage(response));
}
}
public <T> T post(String path, Object requestBody, Class<T> responseClass) throws Exception {
String jsonBody = objectMapper.writeValueAsString(requestBody);
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return handleResponse(response, responseClass);
}
public <T> T put(String path, Object requestBody, Class<T> responseClass) throws Exception {
String jsonBody = objectMapper.writeValueAsString(requestBody);
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return handleResponse(response, responseClass);
}
public void delete(String path) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.DELETE()
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 204 && response.statusCode() != 200) {
throw new RuntimeException(parseErrorMessage(response));
}
}
public void deleteWithBody(String path, Object requestBody) throws Exception {
String jsonBody = objectMapper.writeValueAsString(requestBody);
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.header("Content-Type", "application/json")
.method("DELETE", HttpRequest.BodyPublishers.ofString(jsonBody))
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 204 && response.statusCode() != 200) {
throw new RuntimeException(parseErrorMessage(response));
}
}
private void addAuthHeader(HttpRequest.Builder builder) {
String token = UserSession.getInstance().getJwtToken();
if (token != null && !token.isEmpty()) {
builder.header("Authorization", "Bearer " + token);
}
}
private <T> T handleResponse(HttpResponse<String> response, Class<T> responseClass) throws Exception {
int statusCode = response.statusCode();
if (statusCode == 200 || statusCode == 201) {
if (response.body() == null || response.body().isEmpty()) {
return null;
}
return objectMapper.readValue(response.body(), responseClass);
} else if (statusCode == 204) {
return null;
} else if (statusCode == 401) {
throw new RuntimeException("Authentication failed. Please log in again.");
} else if (statusCode == 403) {
throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
} else {
throw new RuntimeException(parseErrorMessage(response));
}
}
private String parseErrorMessage(HttpResponse<String> response) {
try {
if (response.body() != null && !response.body().isEmpty()) {
var errorNode = objectMapper.readTree(response.body());
if (errorNode.has("message")) {
return errorNode.get("message").asText();
}
if (errorNode.has("errors")) {
StringBuilder sb = new StringBuilder();
errorNode.get("errors").fields().forEachRemaining(entry -> {
sb.append(entry.getValue().asText()).append("\n");
});
return sb.toString().trim();
}
}
} catch (Exception e) {
System.err.println("Error parsing error message: " + e.getMessage());
}
return "Request failed with status " + response.statusCode();
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
}

View File

@@ -0,0 +1,34 @@
package org.example.petshopdesktop.api;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ApiConfig {
private static final ApiConfig INSTANCE = new ApiConfig();
private final String baseUrl;
private ApiConfig() {
Properties props = new Properties();
String url = "http://localhost:8080";
try (InputStream input = getClass().getClassLoader().getResourceAsStream("connectionpetstore.properties")) {
if (input != null) {
props.load(input);
url = props.getProperty("api.baseUrl", "http://localhost:8080");
}
} catch (IOException e) {
System.err.println("Failed to load api.baseUrl from properties: " + e.getMessage());
}
this.baseUrl = url;
}
public static ApiConfig getInstance() {
return INSTANCE;
}
public String getBaseUrl() {
return baseUrl;
}
}

View File

@@ -0,0 +1,45 @@
package org.example.petshopdesktop.api.dto.adoption;
import java.time.LocalDate;
public class AdoptionRequest {
private Long petId;
private Long customerId;
private LocalDate adoptionDate;
private String adoptionStatus;
public AdoptionRequest() {
}
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(LocalDate adoptionDate) {
this.adoptionDate = adoptionDate;
}
public String getAdoptionStatus() {
return adoptionStatus;
}
public void setAdoptionStatus(String adoptionStatus) {
this.adoptionStatus = adoptionStatus;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.adoption;
import java.time.LocalDate;
public class AdoptionResponse {
private Long adoptionId;
private String petName;
private String customerName;
private LocalDate adoptionDate;
private String adoptionStatus;
public AdoptionResponse() {
}
public Long getAdoptionId() {
return adoptionId;
}
public void setAdoptionId(Long adoptionId) {
this.adoptionId = adoptionId;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(LocalDate adoptionDate) {
this.adoptionDate = adoptionDate;
}
public String getAdoptionStatus() {
return adoptionStatus;
}
public void setAdoptionStatus(String adoptionStatus) {
this.adoptionStatus = adoptionStatus;
}
}

View File

@@ -0,0 +1,36 @@
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;
public class DailySales {
private String date;
private BigDecimal revenue;
private Long salesCount;
public DailySales() {
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
public Long getSalesCount() {
return salesCount;
}
public void setSalesCount(Long salesCount) {
this.salesCount = salesCount;
}
}

View File

@@ -0,0 +1,121 @@
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;
import java.util.List;
public class DashboardResponse {
private SalesSummary salesSummary;
private InventorySummary inventorySummary;
private List<TopProduct> topProducts;
private List<DailySales> dailySales;
public DashboardResponse() {
}
public SalesSummary getSalesSummary() {
return salesSummary;
}
public void setSalesSummary(SalesSummary salesSummary) {
this.salesSummary = salesSummary;
}
public InventorySummary getInventorySummary() {
return inventorySummary;
}
public void setInventorySummary(InventorySummary inventorySummary) {
this.inventorySummary = inventorySummary;
}
public List<TopProduct> getTopProducts() {
return topProducts;
}
public void setTopProducts(List<TopProduct> topProducts) {
this.topProducts = topProducts;
}
public List<DailySales> getDailySales() {
return dailySales;
}
public void setDailySales(List<DailySales> dailySales) {
this.dailySales = dailySales;
}
public static class SalesSummary {
private BigDecimal totalRevenue;
private Long totalSales;
private BigDecimal totalRefunds;
private Long totalRefundCount;
public SalesSummary() {
}
public BigDecimal getTotalRevenue() {
return totalRevenue;
}
public void setTotalRevenue(BigDecimal totalRevenue) {
this.totalRevenue = totalRevenue;
}
public Long getTotalSales() {
return totalSales;
}
public void setTotalSales(Long totalSales) {
this.totalSales = totalSales;
}
public BigDecimal getTotalRefunds() {
return totalRefunds;
}
public void setTotalRefunds(BigDecimal totalRefunds) {
this.totalRefunds = totalRefunds;
}
public Long getTotalRefundCount() {
return totalRefundCount;
}
public void setTotalRefundCount(Long totalRefundCount) {
this.totalRefundCount = totalRefundCount;
}
}
public static class InventorySummary {
private Long totalProducts;
private Long lowStockProducts;
private Long outOfStockProducts;
public InventorySummary() {
}
public Long getTotalProducts() {
return totalProducts;
}
public void setTotalProducts(Long totalProducts) {
this.totalProducts = totalProducts;
}
public Long getLowStockProducts() {
return lowStockProducts;
}
public void setLowStockProducts(Long lowStockProducts) {
this.lowStockProducts = lowStockProducts;
}
public Long getOutOfStockProducts() {
return outOfStockProducts;
}
public void setOutOfStockProducts(Long outOfStockProducts) {
this.outOfStockProducts = outOfStockProducts;
}
}
}

View File

@@ -0,0 +1,45 @@
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;
public class TopProduct {
private Long productId;
private String productName;
private Long quantitySold;
private BigDecimal revenue;
public TopProduct() {
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Long getQuantitySold() {
return quantitySold;
}
public void setQuantitySold(Long quantitySold) {
this.quantitySold = quantitySold;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
}

View File

@@ -0,0 +1,65 @@
package org.example.petshopdesktop.api.dto.appointment;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
public class AppointmentRequest {
private List<Long> petIds;
private Long customerId;
private Long serviceId;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
public AppointmentRequest() {
}
public List<Long> getPetIds() {
return petIds;
}
public void setPetIds(List<Long> petIds) {
this.petIds = petIds;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public String getAppointmentStatus() {
return appointmentStatus;
}
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
}

View File

@@ -0,0 +1,73 @@
package org.example.petshopdesktop.api.dto.appointment;
import java.time.LocalDate;
import java.time.LocalTime;
public class AppointmentResponse {
private Long appointmentId;
private String customerName;
private java.util.List<String> petNames;
private String serviceName;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
public AppointmentResponse() {
}
public Long getAppointmentId() {
return appointmentId;
}
public void setAppointmentId(Long appointmentId) {
this.appointmentId = appointmentId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public java.util.List<String> getPetNames() {
return petNames;
}
public void setPetNames(java.util.List<String> petNames) {
this.petNames = petNames;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public String getAppointmentStatus() {
return appointmentStatus;
}
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
}

View File

@@ -0,0 +1,30 @@
package org.example.petshopdesktop.api.dto.auth;
public class LoginRequest {
private String username;
private String password;
public LoginRequest() {
}
public LoginRequest(String username, String password) {
this.username = username;
this.password = password;
}
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;
}
}

View File

@@ -0,0 +1,34 @@
package org.example.petshopdesktop.api.dto.auth;
public class LoginResponse {
private String token;
private String username;
private String role;
public LoginResponse() {
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@@ -0,0 +1,34 @@
package org.example.petshopdesktop.api.dto.auth;
public class UserInfoResponse {
private Long id;
private String username;
private String role;
public UserInfoResponse() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}

View File

@@ -0,0 +1,22 @@
package org.example.petshopdesktop.api.dto.common;
import java.util.List;
public class BulkDeleteRequest {
private List<Long> ids;
public BulkDeleteRequest() {
}
public BulkDeleteRequest(List<Long> ids) {
this.ids = ids;
}
public List<Long> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
this.ids = ids;
}
}

View File

@@ -0,0 +1,25 @@
package org.example.petshopdesktop.api.dto.common;
public class DropdownOption {
private Long id;
private String label;
public DropdownOption() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}

View File

@@ -0,0 +1,72 @@
package org.example.petshopdesktop.api.dto.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class PageResponse<T> {
private List<T> content;
@JsonProperty("number")
private int pageNumber;
@JsonProperty("size")
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
public PageResponse() {
}
public List<T> getContent() {
return content;
}
public void setContent(List<T> content) {
this.content = content;
}
public int getPageNumber() {
return pageNumber;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalElements() {
return totalElements;
}
public void setTotalElements(long totalElements) {
this.totalElements = totalElements;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public boolean isLast() {
return last;
}
public void setLast(boolean last) {
this.last = last;
}
}

View File

@@ -0,0 +1,25 @@
package org.example.petshopdesktop.api.dto.inventory;
public class InventoryRequest {
private Long prodId;
private Integer quantity;
public InventoryRequest() {
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}

View File

@@ -0,0 +1,72 @@
package org.example.petshopdesktop.api.dto.inventory;
import java.time.LocalDateTime;
public class InventoryResponse {
private Long inventoryId;
private Long prodId;
private String productName;
private String categoryName;
private Integer quantity;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public InventoryResponse() {
}
public Long getInventoryId() {
return inventoryId;
}
public void setInventoryId(Long inventoryId) {
this.inventoryId = inventoryId;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
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;
}
}

View File

@@ -0,0 +1,63 @@
package org.example.petshopdesktop.api.dto.pet;
import java.math.BigDecimal;
public class PetRequest {
private String petName;
private String petSpecies;
private String petBreed;
private Integer petAge;
private String petStatus;
private BigDecimal petPrice;
public PetRequest() {
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getPetSpecies() {
return petSpecies;
}
public void setPetSpecies(String petSpecies) {
this.petSpecies = petSpecies;
}
public String getPetBreed() {
return petBreed;
}
public void setPetBreed(String petBreed) {
this.petBreed = petBreed;
}
public Integer getPetAge() {
return petAge;
}
public void setPetAge(Integer petAge) {
this.petAge = petAge;
}
public String getPetStatus() {
return petStatus;
}
public void setPetStatus(String petStatus) {
this.petStatus = petStatus;
}
public BigDecimal getPetPrice() {
return petPrice;
}
public void setPetPrice(BigDecimal petPrice) {
this.petPrice = petPrice;
}
}

View File

@@ -0,0 +1,91 @@
package org.example.petshopdesktop.api.dto.pet;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class PetResponse {
private Long petId;
private String petName;
private String petSpecies;
private String petBreed;
private Integer petAge;
private String petStatus;
private BigDecimal petPrice;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public PetResponse() {
}
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public String getPetName() {
return petName;
}
public void setPetName(String petName) {
this.petName = petName;
}
public String getPetSpecies() {
return petSpecies;
}
public void setPetSpecies(String petSpecies) {
this.petSpecies = petSpecies;
}
public String getPetBreed() {
return petBreed;
}
public void setPetBreed(String petBreed) {
this.petBreed = petBreed;
}
public Integer getPetAge() {
return petAge;
}
public void setPetAge(Integer petAge) {
this.petAge = petAge;
}
public String getPetStatus() {
return petStatus;
}
public void setPetStatus(String petStatus) {
this.petStatus = petStatus;
}
public BigDecimal getPetPrice() {
return petPrice;
}
public void setPetPrice(BigDecimal petPrice) {
this.petPrice = petPrice;
}
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;
}
}

View File

@@ -0,0 +1,45 @@
package org.example.petshopdesktop.api.dto.product;
import java.math.BigDecimal;
public class ProductRequest {
private String prodName;
private Long categoryId;
private BigDecimal prodPrice;
private String prodDesc;
public ProductRequest() {
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public BigDecimal getProdPrice() {
return prodPrice;
}
public void setProdPrice(BigDecimal prodPrice) {
this.prodPrice = prodPrice;
}
public String getProdDesc() {
return prodDesc;
}
public void setProdDesc(String prodDesc) {
this.prodDesc = prodDesc;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.product;
import java.math.BigDecimal;
public class ProductResponse {
private Long prodId;
private String prodName;
private String categoryName;
private BigDecimal prodPrice;
private String prodDesc;
public ProductResponse() {
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public BigDecimal getProdPrice() {
return prodPrice;
}
public void setProdPrice(BigDecimal prodPrice) {
this.prodPrice = prodPrice;
}
public String getProdDesc() {
return prodDesc;
}
public void setProdDesc(String prodDesc) {
this.prodDesc = prodDesc;
}
}

View File

@@ -0,0 +1,36 @@
package org.example.petshopdesktop.api.dto.productsupplier;
import java.math.BigDecimal;
public class ProductSupplierRequest {
private Long productId;
private Long supplierId;
private BigDecimal cost;
public ProductSupplierRequest() {
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.productsupplier;
import java.math.BigDecimal;
public class ProductSupplierResponse {
private Long productId;
private Long supplierId;
private String productName;
private String supplierName;
private BigDecimal cost;
public ProductSupplierResponse() {
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Long getSupplierId() {
return supplierId;
}
public void setSupplierId(Long supplierId) {
this.supplierId = supplierId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getSupplierName() {
return supplierName;
}
public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
}

View File

@@ -0,0 +1,64 @@
package org.example.petshopdesktop.api.dto.purchaseorder;
import java.math.BigDecimal;
import java.time.LocalDate;
public class PurchaseOrderResponse {
private Long purchaseOrderId;
private String supplierName;
private LocalDate orderDate;
private LocalDate expectedDeliveryDate;
private String orderStatus;
private BigDecimal totalAmount;
public PurchaseOrderResponse() {
}
public Long getPurchaseOrderId() {
return purchaseOrderId;
}
public void setPurchaseOrderId(Long purchaseOrderId) {
this.purchaseOrderId = purchaseOrderId;
}
public String getSupplierName() {
return supplierName;
}
public void setSupplierName(String supplierName) {
this.supplierName = supplierName;
}
public LocalDate getOrderDate() {
return orderDate;
}
public void setOrderDate(LocalDate orderDate) {
this.orderDate = orderDate;
}
public LocalDate getExpectedDeliveryDate() {
return expectedDeliveryDate;
}
public void setExpectedDeliveryDate(LocalDate expectedDeliveryDate) {
this.expectedDeliveryDate = expectedDeliveryDate;
}
public String getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
}

View File

@@ -0,0 +1,27 @@
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;
public class SaleItemRequest {
private Long prodId;
private Integer quantity;
public SaleItemRequest() {
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;
public class SaleItemResponse {
private Long saleItemId;
private Long prodId;
private String productName;
private Integer quantity;
private BigDecimal unitPrice;
public SaleItemResponse() {
}
public Long getSaleItemId() {
return saleItemId;
}
public void setSaleItemId(Long saleItemId) {
this.saleItemId = saleItemId;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
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;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.sale;
import java.util.List;
public class SaleRequest {
private Long storeId;
private String paymentMethod;
private List<SaleItemRequest> items;
private Boolean isRefund;
private Long originalSaleId;
public SaleRequest() {
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public List<SaleItemRequest> getItems() {
return items;
}
public void setItems(List<SaleItemRequest> items) {
this.items = items;
}
public Boolean getIsRefund() {
return isRefund;
}
public void setIsRefund(Boolean isRefund) {
this.isRefund = isRefund;
}
public Long getOriginalSaleId() {
return originalSaleId;
}
public void setOriginalSaleId(Long originalSaleId) {
this.originalSaleId = originalSaleId;
}
}

View File

@@ -0,0 +1,92 @@
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
public class SaleResponse {
private Long saleId;
private String employeeName;
private String storeName;
private LocalDateTime saleDate;
private BigDecimal totalAmount;
private String paymentMethod;
private Boolean isRefund;
private Long originalSaleId;
private List<SaleItemResponse> items;
public SaleResponse() {
}
public Long getSaleId() {
return saleId;
}
public void setSaleId(Long saleId) {
this.saleId = saleId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public LocalDateTime getSaleDate() {
return saleDate;
}
public void setSaleDate(LocalDateTime saleDate) {
this.saleDate = saleDate;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public Boolean getIsRefund() {
return isRefund;
}
public void setIsRefund(Boolean isRefund) {
this.isRefund = isRefund;
}
public Long getOriginalSaleId() {
return originalSaleId;
}
public void setOriginalSaleId(Long originalSaleId) {
this.originalSaleId = originalSaleId;
}
public List<SaleItemResponse> getItems() {
return items;
}
public void setItems(List<SaleItemResponse> items) {
this.items = items;
}
}

View File

@@ -0,0 +1,45 @@
package org.example.petshopdesktop.api.dto.service;
import java.math.BigDecimal;
public class ServiceRequest {
private String serviceName;
private BigDecimal servicePrice;
private String serviceDesc;
private Integer serviceDuration;
public ServiceRequest() {
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public BigDecimal getServicePrice() {
return servicePrice;
}
public void setServicePrice(BigDecimal servicePrice) {
this.servicePrice = servicePrice;
}
public String getServiceDesc() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc = serviceDesc;
}
public Integer getServiceDuration() {
return serviceDuration;
}
public void setServiceDuration(Integer serviceDuration) {
this.serviceDuration = serviceDuration;
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.api.dto.service;
import java.math.BigDecimal;
public class ServiceResponse {
private Long serviceId;
private String serviceName;
private BigDecimal servicePrice;
private String serviceDesc;
private Integer serviceDuration;
public ServiceResponse() {
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public BigDecimal getServicePrice() {
return servicePrice;
}
public void setServicePrice(BigDecimal servicePrice) {
this.servicePrice = servicePrice;
}
public String getServiceDesc() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc = serviceDesc;
}
public Integer getServiceDuration() {
return serviceDuration;
}
public void setServiceDuration(Integer serviceDuration) {
this.serviceDuration = serviceDuration;
}
}

View File

@@ -0,0 +1,61 @@
package org.example.petshopdesktop.api.dto.supplier;
public class SupplierRequest {
private String supCompany;
private String supContactFirstName;
private String supContactLastName;
private String supPhone;
private String supEmail;
private String address;
public SupplierRequest() {
}
public String getSupCompany() {
return supCompany;
}
public void setSupCompany(String supCompany) {
this.supCompany = supCompany;
}
public String getSupContactFirstName() {
return supContactFirstName;
}
public void setSupContactFirstName(String supContactFirstName) {
this.supContactFirstName = supContactFirstName;
}
public String getSupContactLastName() {
return supContactLastName;
}
public void setSupContactLastName(String supContactLastName) {
this.supContactLastName = supContactLastName;
}
public String getSupPhone() {
return supPhone;
}
public void setSupPhone(String supPhone) {
this.supPhone = supPhone;
}
public String getSupEmail() {
return supEmail;
}
public void setSupEmail(String supEmail) {
this.supEmail = supEmail;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}

View File

@@ -0,0 +1,70 @@
package org.example.petshopdesktop.api.dto.supplier;
public class SupplierResponse {
private Long supId;
private String supCompany;
private String supContactFirstName;
private String supContactLastName;
private String supPhone;
private String supEmail;
private String address;
public SupplierResponse() {
}
public Long getSupId() {
return supId;
}
public void setSupId(Long supId) {
this.supId = supId;
}
public String getSupCompany() {
return supCompany;
}
public void setSupCompany(String supCompany) {
this.supCompany = supCompany;
}
public String getSupContactFirstName() {
return supContactFirstName;
}
public void setSupContactFirstName(String supContactFirstName) {
this.supContactFirstName = supContactFirstName;
}
public String getSupContactLastName() {
return supContactLastName;
}
public void setSupContactLastName(String supContactLastName) {
this.supContactLastName = supContactLastName;
}
public String getSupPhone() {
return supPhone;
}
public void setSupPhone(String supPhone) {
this.supPhone = supPhone;
}
public String getSupEmail() {
return supEmail;
}
public void setSupEmail(String supEmail) {
this.supEmail = supEmail;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}

View File

@@ -0,0 +1,61 @@
package org.example.petshopdesktop.api.dto.user;
public class UserRequest {
private String username;
private String password;
private String fullName;
private String email;
private String role;
private Boolean active;
public UserRequest() {
}
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 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 getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}

View File

@@ -0,0 +1,81 @@
package org.example.petshopdesktop.api.dto.user;
import java.time.LocalDateTime;
public class UserResponse {
private Long id;
private String username;
private String fullName;
private String email;
private String role;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public UserResponse() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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 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;
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class AdoptionApi {
private static final AdoptionApi INSTANCE = new AdoptionApi();
private final ApiClient apiClient;
private AdoptionApi() {
this.apiClient = ApiClient.getInstance();
}
public static AdoptionApi getInstance() {
return INSTANCE;
}
public List<AdoptionResponse> listAdoptions(String query) throws Exception {
String path = "/api/v1/adoptions?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<AdoptionResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<AdoptionResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from adoptions endpoint");
}
return pageResponse.getContent();
}
public AdoptionResponse createAdoption(AdoptionRequest request) throws Exception {
return apiClient.post("/api/v1/adoptions", request, AdoptionResponse.class);
}
public AdoptionResponse updateAdoption(Long id, AdoptionRequest request) throws Exception {
return apiClient.put("/api/v1/adoptions/" + id, request, AdoptionResponse.class);
}
public void deleteAdoptions(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/adoptions", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,22 @@
package org.example.petshopdesktop.api.endpoints;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
public class AnalyticsApi {
private static final AnalyticsApi INSTANCE = new AnalyticsApi();
private final ApiClient apiClient;
private AnalyticsApi() {
this.apiClient = ApiClient.getInstance();
}
public static AnalyticsApi getInstance() {
return INSTANCE;
}
public DashboardResponse getDashboard(int days, int top) throws Exception {
String path = "/api/v1/analytics/dashboard?days=" + days + "&top=" + top;
return apiClient.get(path, DashboardResponse.class);
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.appointment.AppointmentRequest;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class AppointmentApi {
private static final AppointmentApi INSTANCE = new AppointmentApi();
private final ApiClient apiClient;
private AppointmentApi() {
this.apiClient = ApiClient.getInstance();
}
public static AppointmentApi getInstance() {
return INSTANCE;
}
public List<AppointmentResponse> listAppointments(String query) throws Exception {
String path = "/api/v1/appointments?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<AppointmentResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<AppointmentResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from appointments endpoint");
}
return pageResponse.getContent();
}
public AppointmentResponse createAppointment(AppointmentRequest request) throws Exception {
return apiClient.post("/api/v1/appointments", request, AppointmentResponse.class);
}
public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) throws Exception {
return apiClient.put("/api/v1/appointments/" + id, request, AppointmentResponse.class);
}
public void deleteAppointments(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/appointments", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,76 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import java.util.List;
public class DropdownApi {
private static final DropdownApi INSTANCE = new DropdownApi();
private final ApiClient apiClient;
private DropdownApi() {
this.apiClient = ApiClient.getInstance();
}
public static DropdownApi getInstance() {
return INSTANCE;
}
public List<DropdownOption> getCategories() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/categories");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from categories endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getProducts() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from products endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getSuppliers() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/suppliers");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from suppliers endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getServices() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/services");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from services endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getCustomers() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from customers endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getPets() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from pets endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getStores() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from stores endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
}

View File

@@ -0,0 +1,52 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.inventory.InventoryRequest;
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class InventoryApi {
private static final InventoryApi INSTANCE = new InventoryApi();
private final ApiClient apiClient;
private InventoryApi() {
this.apiClient = ApiClient.getInstance();
}
public static InventoryApi getInstance() {
return INSTANCE;
}
public List<InventoryResponse> listInventory(String query) throws Exception {
String path = "/api/v1/inventory?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<InventoryResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<InventoryResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from inventory endpoint");
}
return pageResponse.getContent();
}
public InventoryResponse createInventory(InventoryRequest request) throws Exception {
return apiClient.post("/api/v1/inventory", request, InventoryResponse.class);
}
public InventoryResponse updateInventory(Long id, InventoryRequest request) throws Exception {
return apiClient.put("/api/v1/inventory/" + id, request, InventoryResponse.class);
}
public void deleteInventory(Long id) throws Exception {
apiClient.delete("/api/v1/inventory/" + id);
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.pet.PetRequest;
import org.example.petshopdesktop.api.dto.pet.PetResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class PetApi {
private static final PetApi INSTANCE = new PetApi();
private final ApiClient apiClient;
private PetApi() {
this.apiClient = ApiClient.getInstance();
}
public static PetApi getInstance() {
return INSTANCE;
}
public List<PetResponse> listPets(String query) throws Exception {
String path = "/api/v1/pets?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<PetResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<PetResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from pets endpoint");
}
return pageResponse.getContent();
}
public PetResponse createPet(PetRequest request) throws Exception {
return apiClient.post("/api/v1/pets", request, PetResponse.class);
}
public PetResponse updatePet(Long id, PetRequest request) throws Exception {
return apiClient.put("/api/v1/pets/" + id, request, PetResponse.class);
}
public void deletePets(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/pets", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.product.ProductRequest;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ProductApi {
private static final ProductApi INSTANCE = new ProductApi();
private final ApiClient apiClient;
private ProductApi() {
this.apiClient = ApiClient.getInstance();
}
public static ProductApi getInstance() {
return INSTANCE;
}
public List<ProductResponse> listProducts(String query) throws Exception {
String path = "/api/v1/products?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<ProductResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<ProductResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from products endpoint");
}
return pageResponse.getContent();
}
public ProductResponse createProduct(ProductRequest request) throws Exception {
return apiClient.post("/api/v1/products", request, ProductResponse.class);
}
public ProductResponse updateProduct(Long id, ProductRequest request) throws Exception {
return apiClient.put("/api/v1/products/" + id, request, ProductResponse.class);
}
public void deleteProducts(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/products", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ProductSupplierApi {
private static final ProductSupplierApi INSTANCE = new ProductSupplierApi();
private final ApiClient apiClient;
private ProductSupplierApi() {
this.apiClient = ApiClient.getInstance();
}
public static ProductSupplierApi getInstance() {
return INSTANCE;
}
public List<ProductSupplierResponse> listProductSuppliers(String query) throws Exception {
String path = "/api/v1/product-suppliers?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<ProductSupplierResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<ProductSupplierResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from product-suppliers endpoint");
}
return pageResponse.getContent();
}
public ProductSupplierResponse createProductSupplier(ProductSupplierRequest request) throws Exception {
return apiClient.post("/api/v1/product-suppliers", request, ProductSupplierResponse.class);
}
public ProductSupplierResponse updateProductSupplier(Long productId, Long supplierId, ProductSupplierRequest request) throws Exception {
return apiClient.put("/api/v1/product-suppliers/" + productId + "/" + supplierId, request, ProductSupplierResponse.class);
}
public void deleteProductSupplier(Long productId, Long supplierId) throws Exception {
apiClient.delete("/api/v1/product-suppliers/" + productId + "/" + supplierId);
}
}

View File

@@ -0,0 +1,39 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class PurchaseOrderApi {
private static final PurchaseOrderApi INSTANCE = new PurchaseOrderApi();
private final ApiClient apiClient;
private PurchaseOrderApi() {
this.apiClient = ApiClient.getInstance();
}
public static PurchaseOrderApi getInstance() {
return INSTANCE;
}
public List<PurchaseOrderResponse> listPurchaseOrders(String query) throws Exception {
String path = "/api/v1/purchase-orders?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<PurchaseOrderResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<PurchaseOrderResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from purchase-orders endpoint");
}
return pageResponse.getContent();
}
}

View File

@@ -0,0 +1,48 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class SaleApi {
private static final SaleApi INSTANCE = new SaleApi();
private final ApiClient apiClient;
private SaleApi() {
this.apiClient = ApiClient.getInstance();
}
public static SaleApi getInstance() {
return INSTANCE;
}
public List<SaleResponse> listSales(int page, int size, String query) throws Exception {
String path = "/api/v1/sales?page=" + page + "&size=" + size;
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<SaleResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<SaleResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from sales endpoint");
}
return pageResponse.getContent();
}
public SaleResponse getSale(Long id) throws Exception {
return apiClient.get("/api/v1/sales/" + id, SaleResponse.class);
}
public SaleResponse createSale(SaleRequest request) throws Exception {
return apiClient.post("/api/v1/sales", request, SaleResponse.class);
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.service.ServiceRequest;
import org.example.petshopdesktop.api.dto.service.ServiceResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ServiceApi {
private static final ServiceApi INSTANCE = new ServiceApi();
private final ApiClient apiClient;
private ServiceApi() {
this.apiClient = ApiClient.getInstance();
}
public static ServiceApi getInstance() {
return INSTANCE;
}
public List<ServiceResponse> listServices(String query) throws Exception {
String path = "/api/v1/services?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<ServiceResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<ServiceResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from services endpoint");
}
return pageResponse.getContent();
}
public ServiceResponse createService(ServiceRequest request) throws Exception {
return apiClient.post("/api/v1/services", request, ServiceResponse.class);
}
public ServiceResponse updateService(Long id, ServiceRequest request) throws Exception {
return apiClient.put("/api/v1/services/" + id, request, ServiceResponse.class);
}
public void deleteServices(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/services", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,53 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.BulkDeleteRequest;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.supplier.SupplierRequest;
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class SupplierApi {
private static final SupplierApi INSTANCE = new SupplierApi();
private final ApiClient apiClient;
private SupplierApi() {
this.apiClient = ApiClient.getInstance();
}
public static SupplierApi getInstance() {
return INSTANCE;
}
public List<SupplierResponse> listSuppliers(String query) throws Exception {
String path = "/api/v1/suppliers?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<SupplierResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<SupplierResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from suppliers endpoint");
}
return pageResponse.getContent();
}
public SupplierResponse createSupplier(SupplierRequest request) throws Exception {
return apiClient.post("/api/v1/suppliers", request, SupplierResponse.class);
}
public SupplierResponse updateSupplier(Long id, SupplierRequest request) throws Exception {
return apiClient.put("/api/v1/suppliers/" + id, request, SupplierResponse.class);
}
public void deleteSuppliers(List<Long> ids) throws Exception {
apiClient.deleteWithBody("/api/v1/suppliers", new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,44 @@
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.common.PageResponse;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class UserApi {
private static final UserApi INSTANCE = new UserApi();
private final ApiClient apiClient;
private UserApi() {
this.apiClient = ApiClient.getInstance();
}
public static UserApi getInstance() {
return INSTANCE;
}
public List<UserResponse> listUsers(String query) throws Exception {
String path = "/api/v1/users?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<UserResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<UserResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from users endpoint");
}
return pageResponse.getContent();
}
public UserResponse createUser(UserRequest request) throws Exception {
return apiClient.post("/api/v1/users", request, UserResponse.class);
}
}

View File

@@ -3,11 +3,13 @@ package org.example.petshopdesktop.auth;
public class UserSession {
private static UserSession instance;
private Integer userId;
private Integer employeeId;
private Long userId;
private Long employeeId;
private String username;
private String employeeName;
private Role role;
private String jwtToken;
private Long storeId;
private UserSession() {}
@@ -18,12 +20,13 @@ public class UserSession {
return instance;
}
public void login(int userId, int employeeId, String username, String employeeName, Role role) {
public void login(Long userId, String username, Role role, String jwtToken) {
this.userId = userId;
this.employeeId = employeeId;
this.employeeId = userId;
this.username = username;
this.employeeName = employeeName;
this.employeeName = username;
this.role = role;
this.jwtToken = jwtToken;
}
public void logout() {
@@ -32,13 +35,15 @@ public class UserSession {
this.username = null;
this.employeeName = null;
this.role = null;
this.jwtToken = null;
this.storeId = null;
}
public Integer getUserId() {
public Long getUserId() {
return userId;
}
public Integer getEmployeeId() {
public Long getEmployeeId() {
return employeeId;
}
@@ -54,6 +59,18 @@ public class UserSession {
return role;
}
public String getJwtToken() {
return jwtToken;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public boolean isLoggedIn() {
return username != null && role != null;
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController;
import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class AdoptionController {
@@ -35,7 +37,7 @@ public class AdoptionController {
private TableColumn<Adoption, Integer> colAdoptionId;
@FXML
private TableColumn<Adoption, Integer> colPetId;
private TableColumn<Adoption, String> colPetId;
@FXML
private TableColumn<Adoption, String> colCustomerName;
@@ -66,7 +68,7 @@ public class AdoptionController {
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
@@ -118,47 +120,24 @@ public class AdoptionController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedAdoptions.stream()
.map(a -> (long) a.getAdoptionId())
.collect(Collectors.toList());
for (Adoption adoption : selectedAdoptions) {
try {
int numRows = AdoptionDB.deleteAdoption(adoption.getAdoptionId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
String.format("Attempting to delete adoption ID %d - foreign key constraint", adoption.getAdoptionId()));
failCount++;
errors.append("Adoption ID ").append(adoption.getAdoptionId()).append(" is referenced in another table\n");
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
String.format("Attempting to delete adoption ID %d", adoption.getAdoptionId()));
failCount++;
errors.append("Failed to delete adoption ID ").append(adoption.getAdoptionId()).append("\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
AdoptionApi.getInstance().deleteAdoptions(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " adoption record(s)");
alert.setContentText("Successfully deleted " + ids.size() + " adoption record(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
"Deleting adoptions");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -181,35 +160,55 @@ public class AdoptionController {
}
private void displayFilteredAdoptions(String filter) {
data.clear();
try {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayAdoptions();
} else {
data = AdoptionDB.getFilteredAdoptions(filter);
tvAdoptions.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions",
e,
"Filtering adoptions with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayAdoptions();
} else {
new Thread(() -> {
try {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions",
e,
"Filtering adoptions with filter: " + filter);
});
}
}).start();
}
}
private void displayAdoptions() {
data.clear();
try {
data = AdoptionDB.getAdoptions();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.displayAdoptions",
e,
"Fetching adoption data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvAdoptions.setItems(data);
new Thread(() -> {
try {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"AdoptionController.displayAdoptions",
e,
"Fetching adoption data for table display");
});
}
}).start();
}
private void openDialog(Adoption adoption, String mode) {
@@ -244,4 +243,17 @@ public class AdoptionController {
btnEdit.setDisable(true);
txtSearch.setText("");
}
private Adoption mapToAdoption(AdoptionResponse response) {
return new Adoption(
response.getAdoptionId().intValue(),
0,
0,
response.getPetName(),
response.getCustomerName(),
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
0.0,
response.getAdoptionStatus()
);
}
}

View File

@@ -1,19 +1,25 @@
package org.example.petshopdesktop.controllers;
import javafx.collections.ObservableList;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.analytics.*;
import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.*;
import java.util.stream.Collectors;
public class AnalyticsController {
@@ -78,79 +84,119 @@ public class AnalyticsController {
private void loadAnalyticsData() {
lblError.setVisible(false);
try {
loadSummaryData();
loadSalesOverTime();
loadTopProductsByRevenue();
loadTopProductsByQuantity();
loadPaymentMethodDistribution();
loadEmployeePerformance();
} catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
new Thread(() -> {
try {
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
Platform.runLater(() -> {
try {
loadSummaryData(dashboard);
loadSalesOverTime(dashboard);
loadTopProductsByRevenue(dashboard);
loadTopProductsByQuantity(dashboard);
loadPaymentMethodDistribution(sales);
loadEmployeePerformance(sales);
} catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
});
}
}).start();
}
private void loadSummaryData(DashboardResponse dashboard) throws Exception {
if (dashboard != null) {
BigDecimal totalRevenue = BigDecimal.ZERO;
Long totalSales = 0L;
Long totalProducts = 0L;
if (dashboard.getSalesSummary() != null) {
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
}
if (dashboard.getInventorySummary() != null) {
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
}
lblTotalRevenue.setText(currency.format(totalRevenue));
lblTotalTransactions.setText(wholeNumber.format(totalSales));
BigDecimal avgTransaction = BigDecimal.ZERO;
if (totalSales > 0) {
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
}
lblAvgTransaction.setText(currency.format(avgTransaction));
lblTotalItems.setText(wholeNumber.format(totalProducts));
}
}
private void loadSummaryData() throws Exception {
SalesSummary summary = SaleDB.getSalesSummary();
if (summary != null) {
lblTotalRevenue.setText(currency.format(summary.getTotalRevenue()));
lblTotalTransactions.setText(wholeNumber.format(summary.getTotalTransactions()));
lblAvgTransaction.setText(currency.format(summary.getAvgTransactionValue()));
lblTotalItems.setText(wholeNumber.format(summary.getTotalItemsSold()));
}
}
private void loadSalesOverTime() throws Exception {
ObservableList<DailySalesData> data = SaleDB.getDailySalesRevenue();
private void loadSalesOverTime(DashboardResponse dashboard) throws Exception {
List<DailySales> dailySales = dashboard.getDailySales() != null ? dashboard.getDailySales() : new ArrayList<>();
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Daily Revenue");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
for (DailySalesData dailySale : data) {
String dateStr = dailySale.getDate().format(formatter);
series.getData().add(new XYChart.Data<>(dateStr, dailySale.getRevenue()));
for (DailySales dailySale : dailySales) {
String dateStr = dailySale.getDate();
BigDecimal revenue = dailySale.getRevenue() != null ? dailySale.getRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(dateStr, revenue));
}
chartSalesOverTime.getData().clear();
chartSalesOverTime.getData().add(series);
}
private void loadTopProductsByRevenue() throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByRevenue(10);
private void loadTopProductsByRevenue(DashboardResponse dashboard) throws Exception {
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Revenue");
for (ProductSalesData product : data) {
series.getData().add(new XYChart.Data<>(product.getTotalRevenue(), product.getProductName()));
for (TopProduct product : topProducts) {
BigDecimal revenue = product.getRevenue() != null ? product.getRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(revenue, product.getProductName()));
}
chartTopRevenue.getData().clear();
chartTopRevenue.getData().add(series);
}
private void loadTopProductsByQuantity() throws Exception {
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByQuantity(10);
private void loadTopProductsByQuantity(DashboardResponse dashboard) throws Exception {
List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
XYChart.Series<Number, String> series = new XYChart.Series<>();
series.setName("Quantity");
for (ProductSalesData product : data) {
series.getData().add(new XYChart.Data<>(product.getTotalQuantity(), product.getProductName()));
for (TopProduct product : topProducts) {
Long quantitySold = product.getQuantitySold() != null ? product.getQuantitySold() : 0L;
series.getData().add(new XYChart.Data<>(quantitySold, product.getProductName()));
}
chartTopQuantity.getData().clear();
chartTopQuantity.getData().add(series);
}
private void loadPaymentMethodDistribution() throws Exception {
ObservableList<PaymentMethodData> data = SaleDB.getPaymentMethodDistribution();
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
Map<String, Long> paymentMethodCount = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.collect(Collectors.groupingBy(
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
Collectors.counting()
));
chartPaymentMethods.getData().clear();
for (PaymentMethodData payment : data) {
for (Map.Entry<String, Long> entry : paymentMethodCount.entrySet()) {
PieChart.Data slice = new PieChart.Data(
payment.getPaymentMethod() + " (" + payment.getTransactionCount() + ")",
payment.getTransactionCount()
entry.getKey() + " (" + entry.getValue() + ")",
entry.getValue()
);
chartPaymentMethods.getData().add(slice);
}
@@ -158,13 +204,20 @@ public class AnalyticsController {
chartPaymentMethods.setLabelsVisible(false);
}
private void loadEmployeePerformance() throws Exception {
ObservableList<EmployeeSalesData> data = SaleDB.getEmployeeSalesPerformance();
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception {
Map<String, Double> employeeRevenue = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.filter(sale -> sale.getEmployeeName() != null)
.collect(Collectors.groupingBy(
SaleResponse::getEmployeeName,
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
));
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Revenue");
for (EmployeeSalesData employee : data) {
series.getData().add(new XYChart.Data<>(employee.getEmployeeName(), employee.getTotalRevenue()));
for (Map.Entry<String, Double> entry : employeeRevenue.entrySet()) {
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
}
chartEmployeePerformance.getData().clear();

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -13,10 +14,14 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.database.AppointmentDB;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class AppointmentController {
@FXML private TableView<AppointmentDTO> tvAppointments;
@@ -71,41 +76,50 @@ public class AppointmentController {
}
private void loadAppointments(){
try{
appointments.setAll(AppointmentDB.getAppointmentDTOs());
}catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.loadAppointments",
e,
"Loading appointments for table display");
e.printStackTrace();
}
new Thread(() -> {
try{
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
});
}catch(Exception e){
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentController.loadAppointments",
e,
"Loading appointments for table display");
e.printStackTrace();
});
}
}).start();
}
private void applyFilter(String text) {
if (filtered == null) {
return;
}
String query = text == null || text.trim().isEmpty() ? null : text.trim();
new Thread(() -> {
try {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.collect(Collectors.toList());
String q = text == null ? "" : text.trim().toLowerCase();
if (q.isEmpty()) {
filtered.setPredicate(a -> true);
return;
}
filtered.setPredicate(a ->
String.valueOf(a.getAppointmentId()).contains(q)
|| safe(a.getPetName()).contains(q)
|| safe(a.getServiceName()).contains(q)
|| safe(a.getAppointmentDate()).contains(q)
|| safe(a.getAppointmentTime()).contains(q)
|| safe(a.getCustomerName()).contains(q)
|| safe(a.getAppointmentStatus()).contains(q)
);
}
private static String safe(String v) {
return v == null ? "" : v.toLowerCase();
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentController.applyFilter",
e,
String.format("Filtering appointments with query: %s", query));
e.printStackTrace();
});
}
}).start();
}
@FXML
@@ -145,35 +159,24 @@ public class AppointmentController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedAppointments.stream()
.map(a -> (long) a.getAppointmentId())
.collect(Collectors.toList());
for (AppointmentDTO appointment : selectedAppointments) {
try{
AppointmentDB.deleteAppointment(appointment.getAppointmentId());
successCount++;
}catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.btnDeleteClicked",
e,
String.format("Attempting to delete appointment ID %d", appointment.getAppointmentId()));
failCount++;
errors.append("Failed to delete appointment ID ").append(appointment.getAppointmentId()).append("\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
AppointmentApi.getInstance().deleteAppointments(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " appointment(s)");
alert.setContentText("Successfully deleted " + ids.size() + " appointment(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentController.btnDeleteClicked",
e,
"Deleting appointments");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -225,4 +228,19 @@ public class AppointmentController {
alert.setContentText(msg);
alert.showAndWait();
}
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
return new AppointmentDTO(
response.getAppointmentId().intValue(),
0,
response.getCustomerName(),
0,
String.join(", ", response.getPetNames()),
0,
response.getServiceName(),
response.getAppointmentDate().toString(),
response.getAppointmentTime().toString(),
response.getAppointmentStatus()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import org.example.petshopdesktop.api.endpoints.InventoryApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class InventoryController {
@@ -58,11 +60,9 @@ public class InventoryController {
//Loads upon view bootup
@FXML
void initialize() {
//Buttons disabled until row is selected
btnEdit.setDisable(true);
btnDelete.setDisable(true);
//Enable multiple selection
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
tvInventory.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.SINGLE);
colInventoryId.setCellValueFactory(new PropertyValueFactory<>("inventoryId"));
colProductId.setCellValueFactory(new PropertyValueFactory<>("prodId"));
@@ -71,19 +71,16 @@ public class InventoryController {
displayInventory();
//Enables buttons when row is selected
tvInventory.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
btnEdit.setDisable(false);
btnDelete.setDisable(false);
});
//Filter as user types
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredInventory(newValue);
});
//EventListener for DELETE key
tvInventory.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvInventory.getSelectionModel().getSelectedItem() != null) {
@@ -100,71 +97,35 @@ public class InventoryController {
openDialog(null, mode);
}
//Prompts user for confirmation prior to deletion
@FXML
void btnDeleteClicked(ActionEvent event) {
//get selected inventory records
var selectedInventory = tvInventory.getSelectionModel().getSelectedItems();
if (selectedInventory.isEmpty()) return;
Inventory selectedInventory = tvInventory.getSelectionModel().getSelectedItem();
if (selectedInventory == null) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedInventory.size() == 1
? "Are you sure you want to delete this inventory record?"
: "Are you sure you want to delete " + selectedInventory.size() + " inventory records?";
question.setContentText(message);
question.setContentText("Are you sure you want to delete this inventory record?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
for (Inventory inventory : selectedInventory) {
try {
int numRows = InventoryDB.deleteInventory(inventory.getInventoryId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
String.format("Attempting to delete inventory ID %d - foreign key constraint", inventory.getInventoryId()));
failCount++;
errors.append("Inventory record '").append(inventory.getProdName()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
String.format("Attempting to delete inventory ID %d", inventory.getInventoryId()));
failCount++;
errors.append("Failed to delete '").append(inventory.getProdName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
InventoryApi.getInstance().deleteInventory((long) selectedInventory.getInventoryId());
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " inventory record(s)");
alert.setContentText("Successfully deleted inventory record");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
"Deleting inventory");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
//refresh display and reset inputs
displayInventory();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
@@ -183,66 +144,72 @@ public class InventoryController {
}
}
//Search filter
private void displayFilteredInventory(String filter) {
data.clear();
try {
//If search box is empty, display all inventory
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayInventory();
}
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayInventory();
} else {
new Thread(() -> {
try {
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(filter);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
else {
data = InventoryDB.getFilteredInventory(filter);
tvInventory.setItems(data);
}
}
catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory",
e,
"Filtering inventory with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
Platform.runLater(() -> {
data.setAll(inventoryList);
tvInventory.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory",
e,
String.format("Filtering inventory with keyword: %s", filter));
});
}
}).start();
}
}
//Displays all records from DB
private void displayInventory() {
data.clear();
try {
data = InventoryDB.getInventory();
}
new Thread(() -> {
try {
List<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(null);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
.collect(Collectors.toList());
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryController.displayInventory",
e,
"Fetching inventory data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvInventory.setItems(data);
Platform.runLater(() -> {
data.setAll(inventoryList);
tvInventory.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"InventoryController.displayInventory",
e,
"Fetching inventory data for table display");
});
}
}).start();
}
//Opens inventory-dialog-view
private void openDialog(Inventory inventory, String mode) {
//Opens FXML
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/inventory-dialog-view.fxml"));
Scene scene = null;
try {
scene = new Scene(fxmlLoader.load());
}
catch (IOException e) {
} catch (IOException e) {
ActivityLogger.getInstance().logException(
"InventoryController.openDialog",
e,
"Loading inventory dialog in " + mode + " mode");
String.format("Loading inventory dialog view in %s mode", mode));
throw new RuntimeException(e);
}
//Passes data and mode to the view
InventoryDialogController dialogController = fxmlLoader.getController();
dialogController.setMode(mode);
@@ -256,10 +223,22 @@ public class InventoryController {
dialogStage.setScene(scene);
dialogStage.showAndWait();
//Refresh inventory
displayInventory();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
private Inventory mapToInventory(InventoryResponse response) {
return new Inventory(
response.getInventoryId().intValue(),
0,
response.getProductName(),
response.getCategoryName() != null ? response.getCategoryName() : "",
0,
"N/A",
response.getQuantity() != null ? response.getQuantity() : 0,
0
);
}
}

View File

@@ -10,13 +10,17 @@ import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.ApiClient;
import org.example.petshopdesktop.api.dto.auth.LoginRequest;
import org.example.petshopdesktop.api.dto.auth.LoginResponse;
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.auth.Role;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.ConnectionDB;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.User;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.util.List;
public class LoginController {
@@ -32,15 +36,6 @@ public class LoginController {
@FXML
public void initialize() {
lblError.setText("");
try {
ConnectionDB.getConnection().close();
try {
UserDB.initializeTable();
} catch (Exception ignored) {
}
} catch (Exception e) {
lblError.setText("Database is not connected. Check Docker and connectionpetstore.properties.");
}
}
@FXML
@@ -54,39 +49,60 @@ public class LoginController {
}
try {
User user = UserDB.authenticate(username, password);
if (user == null) {
lblError.setText("Invalid username or password.");
ApiClient apiClient = ApiClient.getInstance();
LoginRequest loginRequest = new LoginRequest(username, password);
LoginResponse loginResponse = apiClient.post("/api/v1/auth/login", loginRequest, LoginResponse.class);
if (loginResponse == null) {
throw new IllegalStateException("Login response is null");
}
String token = loginResponse.getToken();
String roleStr = loginResponse.getRole();
if (token == null || roleStr == null) {
throw new IllegalStateException("Token or role is null");
}
if ("CUSTOMER".equalsIgnoreCase(roleStr)) {
lblError.setText("Access Denied: Customer accounts cannot access the desktop application.");
txtPassword.clear();
return;
}
UserSession.getInstance().login(
user.getUserId(),
user.getEmployeeId(),
user.getUsername(),
user.getEmployeeFullName(),
user.getRole()
);
Role role = Role.valueOf(roleStr.toUpperCase());
UserSession.getInstance().login(null, username, role, token);
UserInfoResponse userInfo = apiClient.get("/api/v1/auth/me", UserInfoResponse.class);
if (userInfo == null) {
throw new IllegalStateException("User info is null");
}
UserSession.getInstance().login(userInfo.getId(), username, role, token);
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
if (stores != null && !stores.isEmpty()) {
UserSession.getInstance().setStoreId(stores.get(0).getId());
}
openMainLayout();
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"LoginController.btnLoginClicked",
e,
"Authentication attempt for username: " + username);
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
if (msg.contains("doesn't exist") || msg.contains("unknown database") || msg.contains("access denied")) {
lblError.setText("Database error. Check Docker and connectionpetstore.properties.");
String errorMsg = e.getMessage();
if (errorMsg != null && errorMsg.contains("Authentication failed")) {
lblError.setText("Invalid username or password.");
txtPassword.clear();
} else if (e.getCause() instanceof java.net.ConnectException ||
e instanceof java.net.http.HttpConnectTimeoutException) {
lblError.setText("Backend is not reachable, check backend docker compose and port 8080.");
} else {
lblError.setText("Login failed. Check username and password.");
lblError.setText(errorMsg != null ? errorMsg : "Login failed. Please try again.");
}
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException(
"LoginController.btnLoginClicked",
e,
"Database connection");
lblError.setText("Database is not connected. Check Docker and connectionpetstore.properties.");
}
}

View File

@@ -173,8 +173,14 @@ public class MainLayoutController {
@FXML
void logoClicked(MouseEvent event) {
loadView("analytics-view.fxml");
updateButtons(btnAnalytics);
UserSession session = UserSession.getInstance();
if (session.isAdmin()) {
loadView("analytics-view.fxml");
updateButtons(btnAnalytics);
} else {
loadView("sale-view.fxml");
updateButtons(btnSalesHistory);
}
}
@FXML
@@ -201,8 +207,14 @@ public class MainLayoutController {
public void initialize() {
applyRBAC();
loadView("analytics-view.fxml");
updateButtons(btnAnalytics);
UserSession session = UserSession.getInstance();
if (session.isAdmin()) {
loadView("analytics-view.fxml");
updateButtons(btnAnalytics);
} else {
loadView("sale-view.fxml");
updateButtons(btnSalesHistory);
}
}
private void applyRBAC() {
@@ -241,6 +253,11 @@ public class MainLayoutController {
separatorAdmin.setManaged(isAdmin);
}
if (btnAnalytics != null) {
btnAnalytics.setVisible(isAdmin);
btnAnalytics.setManaged(isAdmin);
}
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,16 +11,18 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.pet.PetResponse;
import org.example.petshopdesktop.api.endpoints.PetApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController;
import org.example.petshopdesktop.database.PetDB;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class PetController {
@@ -83,48 +86,24 @@ public class PetController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedPets.stream()
.map(p -> (long) p.getPetId())
.collect(Collectors.toList());
for (Pet pet : selectedPets) {
try{
int numRows = PetDB.deletePet(pet.getPetId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"PetController.btnDeleteClicked",
e,
String.format("Attempting to delete pet ID %d - foreign key constraint", pet.getPetId()));
failCount++;
errors.append("Pet '").append(pet.getPetName()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"PetController.btnDeleteClicked",
e,
String.format("Attempting to delete pet ID %d", pet.getPetId()));
failCount++;
errors.append("Failed to delete '").append(pet.getPetName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
PetApi.getInstance().deletePets(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " pet(s)");
alert.setContentText("Successfully deleted " + ids.size() + " pet(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"PetController.btnDeleteClicked",
e,
"Deleting pets");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -187,39 +166,55 @@ public class PetController {
}
private void displayFilteredPet(String filter) {
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayPets();
}
else {
data = PetDB.getFilteredPets(filter);
tvPets.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"PetController.displayFilteredPet",
e,
"Filtering pets with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayPets();
} else {
new Thread(() -> {
try {
List<PetResponse> pets = PetApi.getInstance().listPets(filter);
List<Pet> petList = pets.stream()
.map(this::mapToPet)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(petList);
tvPets.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"PetController.displayFilteredPet",
e,
String.format("Filtering pets with keyword: %s", filter));
});
}
}).start();
}
}
private void displayPets() {
data.clear();
new Thread(() -> {
try {
List<PetResponse> pets = PetApi.getInstance().listPets(null);
List<Pet> petList = pets.stream()
.map(this::mapToPet)
.collect(Collectors.toList());
try{
data = PetDB.getPets();
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"PetController.displayPets",
e,
"Fetching pet data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvPets.setItems(data);
Platform.runLater(() -> {
data.setAll(petList);
tvPets.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"PetController.displayPets",
e,
"Fetching pet data for table display");
});
}
}).start();
}
private void openDialog(Pet pet, String mode){
@@ -261,4 +256,20 @@ public class PetController {
txtSearch.setText("");
}
private Pet mapToPet(PetResponse response) {
int age = 0;
if (null != null) {
age = Period.between(null, LocalDate.now()).getYears();
}
return new Pet(
response.getPetId().intValue(),
response.getPetName(),
response.getPetSpecies(),
response.getPetBreed(),
age,
response.getPetStatus(),
response.getPetPrice().doubleValue()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -11,18 +12,16 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* The controller for any operations in the products view
@@ -110,22 +109,27 @@ public class ProductController {
* Display the productDTO to table view
*/
private void displayProduct(){
//Erase old content
data.clear();
new Thread(() -> {
try {
List<ProductResponse> products = ProductApi.getInstance().listProducts(null);
List<ProductDTO> productDTOs = products.stream()
.map(this::mapToProductDTO)
.collect(Collectors.toList());
//get Products from database
try{
data = ProductDB.getProductDTO();
} catch (SQLException e) {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductController.displayProduct",
e,
"Fetching product data for table display");
}
//put data in the table
tvProducts.setItems(data);
Platform.runLater(() -> {
data.setAll(productDTOs);
tvProducts.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductController.displayProduct",
e,
"Fetching product data for table display");
});
}
}).start();
}
/**
@@ -160,48 +164,24 @@ public class ProductController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedProducts.stream()
.map(p -> (long) p.getProdId())
.collect(Collectors.toList());
for (ProductDTO product : selectedProducts) {
try{
int numRows = ProductDB.deleteProduct(product.getProdId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductController.btnDeleteClicked",
e,
String.format("Attempting to delete product ID %d - foreign key constraint", product.getProdId()));
failCount++;
errors.append("Product '").append(product.getProdName()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductController.btnDeleteClicked",
e,
String.format("Attempting to delete product ID %d", product.getProdId()));
failCount++;
errors.append("Failed to delete '").append(product.getProdName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
ProductApi.getInstance().deleteProducts(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " product(s)");
alert.setContentText("Successfully deleted " + ids.size() + " product(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductController.btnDeleteClicked",
e,
"Deleting products");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -233,22 +213,30 @@ public class ProductController {
* @param filter word to filter table
*/
private void displayFilteredProduct(String filter){
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProduct(); //If search bar is empty just display everything
}
else{
//Filter the using the keyword
data = ProductDB.getFilteredProductDTOs(filter);
tvProducts.setItems(data);
}
} catch (Exception e) {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductController.displayFilteredProduct",
e,
String.format("Filtering products with keyword: %s", filter));
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProduct();
} else {
new Thread(() -> {
try {
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter);
List<ProductDTO> productDTOs = products.stream()
.map(this::mapToProductDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(productDTOs);
tvProducts.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductController.displayFilteredProduct",
e,
String.format("Filtering products with keyword: %s", filter));
});
}
}).start();
}
}
@@ -297,4 +285,15 @@ public class ProductController {
txtSearch.setText("");
}
private ProductDTO mapToProductDTO(ProductResponse response) {
return new ProductDTO(
response.getProdId().intValue(),
response.getProdName(),
response.getProdPrice().doubleValue(),
0,
response.getCategoryName(),
response.getProdDesc()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,19 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductSupplierDialogController;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.ProductSupplierDB;
import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ProductSupplierController {
@@ -107,22 +105,27 @@ public class ProductSupplierController {
* Display the ProductSupplierDTO to table view
*/
private void displayProductSupplier() {
//Erase old content
data.clear();
new Thread(() -> {
try {
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(null);
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
.map(this::mapToProductSupplierDTO)
.collect(Collectors.toList());
//get ProductSupplier from database
try{
data = ProductSupplierDB.getProductSupplierDTO();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayProductSupplier",
e,
"Fetching product-supplier data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
//put data in the table
tvProductSuppliers.setItems(data);
Platform.runLater(() -> {
data.setAll(productSupplierDTOs);
tvProductSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayProductSupplier",
e,
"Fetching product-supplier data for table display");
});
}
}).start();
}
/**
@@ -130,22 +133,30 @@ public class ProductSupplierController {
* @param filter word to filter table
*/
private void displayFilteredProductSupplier(String filter){
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProductSupplier(); //If search bar is empty just display everything
}
else{
//Filter the using the keyword
data = ProductSupplierDB.getFilteredProductSupplierDTO(filter);
tvProductSuppliers.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayFilteredProductSupplier",
e,
"Filtering product-supplier data with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displayProductSupplier();
} else {
new Thread(() -> {
try {
List<ProductSupplierResponse> productSuppliers = ProductSupplierApi.getInstance().listProductSuppliers(filter);
List<ProductSupplierDTO> productSupplierDTOs = productSuppliers.stream()
.map(this::mapToProductSupplierDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(productSupplierDTOs);
tvProductSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ProductSupplierController.displayFilteredProductSupplier",
e,
"Filtering product-supplier data with filter: " + filter);
});
}
}).start();
}
}
@@ -181,52 +192,36 @@ public class ProductSupplierController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
int deleteCount = 0;
Exception lastException = null;
for (ProductSupplierDTO productSupplier : selectedProductSuppliers) {
try{
int numRows = ProductSupplierDB.deleteProductSupplier(productSupplier.getSupId(), productSupplier.getProdId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
for (ProductSupplierDTO ps : selectedProductSuppliers) {
try {
ProductSupplierApi.getInstance().deleteProductSupplier(
(long) ps.getProdId(),
(long) ps.getSupId()
);
deleteCount++;
} catch (Exception e) {
lastException = e;
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete product-supplier - SupID: %d, ProdID: %d - foreign key constraint",
productSupplier.getSupId(), productSupplier.getProdId()));
failCount++;
errors.append(String.format("Product-Supplier '%s - %s' is referenced in another table\n",
productSupplier.getProdName(), productSupplier.getSupCompany()));
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete product-supplier - SupID: %d, ProdID: %d",
productSupplier.getSupId(), productSupplier.getProdId()));
failCount++;
errors.append(String.format("Failed to delete '%s - %s'\n",
productSupplier.getProdName(), productSupplier.getSupCompany()));
"Deleting product-supplier with productId=" + ps.getProdId() + ", supplierId=" + ps.getSupId());
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
if (deleteCount > 0) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " product-supplier(s)");
alert.setContentText("Successfully deleted " + deleteCount + " product-supplier(s)");
alert.showAndWait();
}
if (lastException != null && deleteCount < selectedProductSuppliers.size()) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Partially Failed");
alert.setContentText("Deleted " + deleteCount + " of " + selectedProductSuppliers.size() + " product-supplier(s). Last error: " + lastException.getMessage());
alert.showAndWait();
}
@@ -297,4 +292,14 @@ public class ProductSupplierController {
txtSearch.setText("");
}
private ProductSupplierDTO mapToProductSupplierDTO(ProductSupplierResponse response) {
return new ProductSupplierDTO(
response.getSupplierId().intValue(),
response.getProductId().intValue(),
response.getSupplierName(),
response.getProductName(),
response.getCost().doubleValue()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -7,9 +8,13 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import org.example.petshopdesktop.database.PurchaseOrderDB;
import org.example.petshopdesktop.api.dto.purchaseorder.PurchaseOrderResponse;
import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.stream.Collectors;
public class PurchaseOrderController {
@FXML private Button btnRefresh;
@@ -19,7 +24,7 @@ public class PurchaseOrderController {
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders;
@FXML private TableColumn<PurchaseOrderDTO,Integer> colOrderId;
@FXML private TableColumn<PurchaseOrderDTO,Long> colOrderId;
@FXML private TableColumn<PurchaseOrderDTO,String> colSupplier;
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
@@ -53,17 +58,28 @@ public class PurchaseOrderController {
}
private void loadPurchaseOrders() {
try {
purchaseOrders.setAll(PurchaseOrderDB.getPurchaseOrders());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"PurchaseOrderController.loadPurchaseOrders",
e,
"Loading purchase orders for table display");
e.printStackTrace();
new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait();
}
new Thread(() -> {
try {
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
List<PurchaseOrderDTO> dtos = responses.stream()
.map(this::mapToPurchaseOrderDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
purchaseOrders.setAll(dtos);
tvPurchaseOrders.setItems(filtered);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"PurchaseOrderController.loadPurchaseOrders",
e,
"Loading purchase orders for table display");
new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait();
});
}
}).start();
}
private void applyFilter(String text) {
@@ -93,4 +109,13 @@ public class PurchaseOrderController {
void btnRefresh() {
loadPurchaseOrders();
}
private PurchaseOrderDTO mapToPurchaseOrderDTO(PurchaseOrderResponse response) {
return new PurchaseOrderDTO(
response.getPurchaseOrderId(),
response.getSupplierName(),
response.getOrderDate() != null ? response.getOrderDate().toString() : "",
response.getOrderStatus()
);
}
}

View File

@@ -22,20 +22,25 @@ import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.Inventory;
import javafx.concurrent.Task;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.HashMap;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SaleController {
@@ -124,8 +129,8 @@ public class SaleController {
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales;
private final Map<Integer, Integer> inventoryByProdId = new HashMap<>();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@FXML
public void initialize() {
@@ -133,7 +138,6 @@ public class SaleController {
setupCreateSale();
applyRoleMode();
refreshInventory();
refreshSales();
}
@@ -170,11 +174,20 @@ public class SaleController {
updateCartTotal();
try {
cbProduct.setItems(ProductDB.getProducts());
} catch (SQLException e) {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
}
cbProduct.setItems(products);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Database connection");
}
}
@@ -185,42 +198,61 @@ public class SaleController {
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
}
private void refreshInventory() {
inventoryByProdId.clear();
try {
for (Inventory inv : InventoryDB.getInventory()) {
inventoryByProdId.put(inv.getProdId(), inv.getQuantity());
}
} catch (SQLException e) {
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Loading inventory");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.refreshInventory", e, "Database connection");
}
}
private void refreshSales() {
refreshSales(false);
}
private void refreshSales(boolean showErrorDialog) {
try {
saleItems.setAll(SaleDB.getSaleLineItems());
} catch (SQLException e) {
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Loading sales");
if (showErrorDialog) {
showError("Sales", "Could not load sales.");
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@Override
protected List<SaleLineItem> call() throws Exception {
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
List<SaleLineItem> lineItems = new ArrayList<>();
for (SaleResponse sale : sales) {
String saleDate = sale.getSaleDate() != null
? sale.getSaleDate().format(DATE_FORMATTER)
: "";
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
for (SaleItemResponse item : sale.getItems()) {
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
double lineTotal = unitPrice * item.getQuantity();
lineItems.add(new SaleLineItem(
sale.getSaleId().intValue(),
saleDate,
sale.getEmployeeName(),
item.getProductName(),
item.getQuantity(),
unitPrice,
lineTotal,
sale.getPaymentMethod(),
sale.getIsRefund() != null && sale.getIsRefund()
));
}
}
}
return lineItems;
}
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.refreshSales", e, "Database connection");
};
task.setOnSucceeded(event -> {
saleItems.setAll(task.getValue());
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
if (showErrorDialog) {
showError("Sales", "Database is not connected.");
showError("Sales", "Could not load sales: " + e.getMessage());
}
}
});
new Thread(task).start();
}
@FXML
void btnRefresh(ActionEvent event) {
refreshInventory();
refreshSales(true);
}
@@ -244,18 +276,6 @@ public class SaleController {
return;
}
int stock = inventoryByProdId.getOrDefault(product.getProdId(), 0);
int alreadyInCart = cartItems.stream()
.filter(i -> i.getProdId() == product.getProdId())
.mapToInt(SaleCartItem::getQuantity)
.sum();
int available = stock - alreadyInCart;
if (requestedQty > available) {
showError("Create Sale", "Not enough stock. Available: " + Math.max(0, available));
return;
}
for (SaleCartItem item : cartItems) {
if (item.getProdId() == product.getProdId()) {
item.setQuantity(item.getQuantity() + requestedQty);
@@ -291,9 +311,9 @@ public class SaleController {
return;
}
Integer employeeId = UserSession.getInstance().getEmployeeId();
if (employeeId == null || employeeId <= 0) {
showError("Create Sale", "Employee is not set for this account.");
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
showError("Create Sale", "Store is not set for this account.");
return;
}
@@ -309,20 +329,34 @@ public class SaleController {
}
try {
int saleId = SaleDB.createSale(employeeId, payment, cartItems);
showInfo("Sale saved", "Sale ID " + saleId + " was created.");
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProdId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequests.add(itemRequest);
}
request.setItems(itemRequests);
SaleResponse response = SaleApi.getInstance().createSale(request);
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
cartItems.clear();
updateCartTotal();
refreshInventory();
refreshSales(true);
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
showError("Create Sale", e.getMessage() == null ? "Could not save the sale." : e.getMessage());
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Database connection");
showError("Create Sale", "Database is not connected.");
String errorMsg = e.getMessage();
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
showError("Create Sale", "Insufficient stock for one or more items.");
} else {
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
}
}
}
@@ -343,7 +377,6 @@ public class SaleController {
dialog.setResizable(false);
dialog.showAndWait();
refreshInventory();
refreshSales(true);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.openRefundDialog", e, "Opening refund dialog");

View File

@@ -1,8 +1,8 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@@ -10,12 +10,17 @@ import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.api.dto.service.ServiceResponse;
import org.example.petshopdesktop.api.endpoints.ServiceApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController;
import org.example.petshopdesktop.util.ActivityLogger;
import javafx.stage.Modality;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ServiceController {
@@ -23,22 +28,23 @@ public class ServiceController {
@FXML private Button btnDelete;
@FXML private Button btnEdit;
@FXML private TableColumn<Service, Integer> colServiceId;
@FXML private TableColumn<Service, String> colServiceName;
@FXML private TableColumn<Service, String> colServiceDesc;
@FXML private TableColumn<Service, Integer> colServiceDuration;
@FXML private TableColumn<Service, Double> colServicePrice;
@FXML private TableColumn<ServiceDTO, Integer> colServiceId;
@FXML private TableColumn<ServiceDTO, String> colServiceName;
@FXML private TableColumn<ServiceDTO, String> colServiceDesc;
@FXML private TableColumn<ServiceDTO, Integer> colServiceDuration;
@FXML private TableColumn<ServiceDTO, Double> colServicePrice;
@FXML private TableView<Service> tvServices;
@FXML private TableView<ServiceDTO> tvServices;
@FXML private TextField txtSearch;
private final ObservableList<Service> services = FXCollections.observableArrayList();
private FilteredList<Service> filtered;
private ObservableList<ServiceDTO> data = FXCollections.observableArrayList();
private String mode = null;
@FXML
public void initialize() {
//Enable multiple selection
btnEdit.setDisable(true);
btnDelete.setDisable(true);
tvServices.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId"));
@@ -47,14 +53,19 @@ public class ServiceController {
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice"));
filtered = new FilteredList<>(services, s -> true);
tvServices.setItems(filtered);
displayServices();
if (txtSearch != null) {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
}
tvServices.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
btnEdit.setDisable(false);
btnDelete.setDisable(false);
}
);
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredServices(newValue);
});
//EventListener for DELETE key
tvServices.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvServices.getSelectionModel().getSelectedItem() != null) {
@@ -62,75 +73,82 @@ public class ServiceController {
}
}
});
loadServices();
}
private void loadServices() {
try {
services.setAll(ServiceDB.getServices());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.loadServices",
e,
"Loading services for table display");
showAlert("Database Error", "Unable to load services.");
e.printStackTrace();
}
private void displayServices() {
new Thread(() -> {
try {
List<ServiceResponse> services = ServiceApi.getInstance().listServices(null);
List<ServiceDTO> serviceDTOs = services.stream()
.map(this::mapToServiceDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(serviceDTOs);
tvServices.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ServiceController.displayServices",
e,
"Fetching service data for table display");
});
}
}).start();
}
private void applyFilter(String text) {
if (filtered == null) {
return;
private void displayFilteredServices(String filter) {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()) {
displayServices();
} else {
new Thread(() -> {
try {
List<ServiceResponse> services = ServiceApi.getInstance().listServices(filter);
List<ServiceDTO> serviceDTOs = services.stream()
.map(this::mapToServiceDTO)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(serviceDTOs);
tvServices.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"ServiceController.displayFilteredServices",
e,
String.format("Filtering services with keyword: %s", filter));
});
}
}).start();
}
String q = text == null ? "" : text.trim().toLowerCase();
if (q.isEmpty()) {
filtered.setPredicate(s -> true);
return;
}
filtered.setPredicate(s ->
String.valueOf(s.getServiceId()).contains(q)
|| safe(s.getServiceName()).contains(q)
|| safe(s.getServiceDesc()).contains(q)
|| String.valueOf(s.getServiceDuration()).contains(q)
|| String.valueOf(s.getServicePrice()).contains(q)
);
}
private static String safe(String v) {
return v == null ? "" : v.toLowerCase();
}
@FXML
void btnAddClicked(ActionEvent event) {
openDialog(null, "Add");
loadServices();
mode = "Add";
openDialog(null, mode);
}
@FXML
void btnEditClicked(ActionEvent event) {
ServiceDTO selected = tvServices.getSelectionModel().getSelectedItem();
Service selected = tvServices.getSelectionModel().getSelectedItem();
if (selected == null) {
showAlert("Select Service", "Please select a service to edit.");
return;
if (selected != null) {
mode = "Edit";
openDialog(selected, mode);
}
openDialog(selected, "Edit");
loadServices();
}
@FXML
void btnDeleteClicked(ActionEvent e) {
//get selected services
void btnDeleteClicked(ActionEvent event) {
var selectedServices = tvServices.getSelectionModel().getSelectedItems();
if (selectedServices.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedServices.size() == 1
@@ -138,82 +156,78 @@ public class ServiceController {
: "Are you sure you want to delete " + selectedServices.size() + " services?";
question.setContentText(message);
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
java.util.Optional<ButtonType> result = question.showAndWait();
Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedServices.stream()
.map(s -> (long) s.getServiceId())
.collect(Collectors.toList());
for (Service service : selectedServices) {
try {
ServiceDB.deleteService(service.getServiceId());
successCount++;
} catch (Exception ex) {
ActivityLogger.getInstance().logException(
"ServiceController.btnDeleteClicked",
ex,
String.format("Attempting to delete service ID %d", service.getServiceId()));
failCount++;
errors.append("Failed to delete '").append(service.getServiceName()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
ServiceApi.getInstance().deleteServices(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " service(s)");
alert.setContentText("Successfully deleted " + ids.size() + " service(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.btnDeleteClicked",
e,
"Deleting services");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
//refresh display
loadServices();
displayServices();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
}
private void openDialog(Service service, String mode) {
private void openDialog(ServiceDTO service, String mode) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml"));
Scene scene = null;
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml")
);
Stage stage = new Stage();
stage.setScene(new Scene(loader.load()));
ServiceDialogController controller = loader.getController();
controller.setMode(mode);
if (mode.equals("Edit")) {
controller.setService(service);
}
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
loadServices();
scene = new Scene(fxmlLoader.load());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.openDialog",
e,
"Opening service dialog in " + mode + " mode");
e.printStackTrace();
String.format("Loading service dialog view in %s mode", mode));
throw new RuntimeException(e);
}
ServiceDialogController dialogController = fxmlLoader.getController();
dialogController.setMode(mode);
if (mode.equals("Edit")) {
dialogController.setService(service);
}
Stage dialogStage = new Stage();
dialogStage.initModality(Modality.APPLICATION_MODAL);
if (mode.equals("Add")) {
dialogStage.setTitle("Add Service");
} else {
dialogStage.setTitle("Edit Service");
}
dialogStage.setScene(scene);
dialogStage.showAndWait();
displayServices();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
txtSearch.setText("");
}
private void showAlert(String title, String msg) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(msg);
alert.showAndWait();
private ServiceDTO mapToServiceDTO(ServiceResponse response) {
return new ServiceDTO(
response.getServiceId().intValue(),
response.getServiceName(),
response.getServiceDesc(),
response.getServiceDuration() != null ? response.getServiceDuration() : 0,
response.getServicePrice().doubleValue()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
@@ -15,12 +16,16 @@ import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;
public class StaffAccountsController {
@@ -107,15 +112,54 @@ public class StaffAccountsController {
private void refresh() {
lblError.setText("");
try {
staffAccounts.setAll(UserDB.getStaffAccounts());
} catch (SQLException e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
lblError.setText("Could not load staff accounts.");
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Database connection");
lblError.setText("Database is not connected.");
tvStaff.setDisable(true);
new Thread(() -> {
try {
List<UserResponse> users = UserApi.getInstance().listUsers(null);
List<StaffAccount> accounts = users.stream()
.map(this::mapToStaffAccount)
.collect(Collectors.toList());
Platform.runLater(() -> {
staffAccounts.setAll(accounts);
tvStaff.setDisable(false);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
Platform.runLater(() -> {
lblError.setText("Could not load staff accounts.");
tvStaff.setDisable(false);
});
}
}).start();
}
private StaffAccount mapToStaffAccount(UserResponse user) {
long id = user.getId() != null ? user.getId() : 0L;
String username = user.getUsername();
String fullName = user.getFullName() != null ? user.getFullName() : "";
String[] names = splitFullName(fullName);
String firstName = names[0];
String lastName = names[1];
String email = user.getEmail() != null ? user.getEmail() : "";
String phone = "";
boolean active = user.getActive() != null ? user.getActive() : false;
Timestamp createdAt = user.getCreatedAt() != null
? Timestamp.from(user.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())
: null;
return new StaffAccount(id, id, username, firstName, lastName, email, phone, active, createdAt);
}
private String[] splitFullName(String fullName) {
if (fullName == null || fullName.trim().isEmpty()) {
return new String[]{"", ""};
}
String[] parts = fullName.trim().split("\\s+", 2);
String firstName = parts.length > 0 ? parts[0] : "";
String lastName = parts.length > 1 ? parts[1] : "";
return new String[]{firstName, lastName};
}
private void applyFilter(String text) {

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -10,15 +11,16 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
import org.example.petshopdesktop.api.endpoints.SupplierApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* The controller for any operations in the supplier view
@@ -105,19 +107,27 @@ public class SupplierController {
* Display the suppliers to table view
*/
private void displaySupplier(){
data.clear();
new Thread(() -> {
try {
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(null);
List<Supplier> supplierList = suppliers.stream()
.map(this::mapToSupplier)
.collect(Collectors.toList());
try{
data = SupplierDB.getSuppliers();
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierController.displaySupplier",
e,
"Fetching supplier data for table display");
System.out.println("Error while fetching table data: " + e.getMessage());
}
tvSuppliers.setItems(data);
Platform.runLater(() -> {
data.setAll(supplierList);
tvSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"SupplierController.displaySupplier",
e,
"Fetching supplier data for table display");
});
}
}).start();
}
/**
@@ -125,22 +135,30 @@ public class SupplierController {
* @param filter word to filter table
*/
private void displayFilteredSupplier(String filter){
data.clear();
try{
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displaySupplier(); //If search bar is empty just display everything
}
else{
//Filter the using the keyword
data = SupplierDB.getFilteredSuppliers(filter);
tvSuppliers.setItems(data);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierController.displayFilteredSupplier",
e,
"Filtering suppliers with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage());
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
displaySupplier();
} else {
new Thread(() -> {
try {
List<SupplierResponse> suppliers = SupplierApi.getInstance().listSuppliers(filter);
List<Supplier> supplierList = suppliers.stream()
.map(this::mapToSupplier)
.collect(Collectors.toList());
Platform.runLater(() -> {
data.setAll(supplierList);
tvSuppliers.setItems(data);
});
} catch (Exception e) {
Platform.runLater(() -> {
System.out.println("Error while fetching table data: " + e.getMessage());
ActivityLogger.getInstance().logException(
"SupplierController.displayFilteredSupplier",
e,
"Filtering suppliers with filter: " + filter);
});
}
}).start();
}
}
@@ -177,48 +195,24 @@ public class SupplierController {
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
int successCount = 0;
int failCount = 0;
StringBuilder errors = new StringBuilder();
List<Long> ids = selectedSuppliers.stream()
.map(s -> (long) s.getSupId())
.collect(Collectors.toList());
for (Supplier supplier : selectedSuppliers) {
try{
int numRows = SupplierDB.deleteSupplier(supplier.getSupId());
if (numRows > 0) {
successCount++;
} else {
failCount++;
}
}
catch (SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete supplier ID %d - foreign key constraint", supplier.getSupId()));
failCount++;
errors.append("Supplier '").append(supplier.getSupCompany()).append("' is referenced in another table\n");
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
String.format("Attempting to delete supplier ID %d", supplier.getSupId()));
failCount++;
errors.append("Failed to delete '").append(supplier.getSupCompany()).append("'\n");
}
}
//show results
if (failCount > 0) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText("Delete Operation Completed with Errors");
alert.setContentText(String.format("Deleted: %d\nFailed: %d\n\n%s",
successCount, failCount, errors.toString()));
alert.showAndWait();
} else if (successCount > 0) {
try {
SupplierApi.getInstance().deleteSuppliers(ids);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Database Operation Confirmed");
alert.setContentText("Successfully deleted " + successCount + " supplier(s)");
alert.setContentText("Successfully deleted " + ids.size() + " supplier(s)");
alert.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
"Deleting suppliers");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Delete Operation Failed");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
@@ -290,4 +284,20 @@ public class SupplierController {
txtSearch.setText("");
}
private Supplier mapToSupplier(SupplierResponse response) {
String contactPerson = response.getSupContactFirstName() + " " + response.getSupContactLastName() != null ? response.getSupContactFirstName() + " " + response.getSupContactLastName() : "";
String[] nameParts = contactPerson.split(" ", 2);
String firstName = nameParts.length > 0 ? nameParts[0] : "";
String lastName = nameParts.length > 1 ? nameParts[1] : "";
return new Supplier(
response.getSupId().intValue(),
response.getSupCompany(),
firstName,
lastName,
response.getSupEmail(),
response.getSupPhone()
);
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -12,16 +13,15 @@ import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.database.PetDB;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.models.Customer;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
public class AdoptionDialogController {
@@ -36,10 +36,10 @@ public class AdoptionDialogController {
private ComboBox<String> cbAdoptionStatus;
@FXML
private ComboBox<Customer> cbCustomer;
private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<Pet> cbPet;
private ComboBox<DropdownOption> cbPet;
@FXML
private DatePicker dpAdoptionDate;
@@ -58,52 +58,51 @@ public class AdoptionDialogController {
"Pending", "Completed", "Cancelled"
);
//Loads upon boot
@FXML
void initialize() {
//Loads statusList into combo box
cbAdoptionStatus.setItems(statusList);
//Pet objects are converted into readable text for combobox (PetID + PetName)
cbPet.setConverter(new StringConverter<Pet>() {
@Override
public String toString(Pet pet) {
return pet == null ? "" : pet.getPetId() + ": " + pet.getPetName();
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
cbPet.setItems(petsObs);
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading pets for combo box");
System.out.println("Error loading pets: " + e.getMessage());
});
}
}).start();
//Not used
@Override
public Pet fromString(String string) { return null; }
});
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
if (customers != null) {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading customers for combo box");
System.out.println("Error loading customers: " + e.getMessage());
});
}
}).start();
//Load pets from DB into pet combobox
try {
cbPet.setItems(PetDB.getPets());
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading pets for combo box");
System.out.println("Error loading pets: " + e.getMessage());
}
//Load customers from DB into customer combobox
try {
cbCustomer.setItems(AdoptionDB.getCustomers());
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading customers for combo box");
System.out.println("Error loading customers: " + e.getMessage());
}
//Save button handler
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -111,7 +110,6 @@ public class AdoptionDialogController {
}
});
//Cancel button handler, closes dialog view
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -120,12 +118,9 @@ public class AdoptionDialogController {
});
}
//Handles logic when clicking Save
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0;
String errorMsg = "";
//Validation: checks if anything is missing
if (cbPet.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Pet is required.\n";
}
@@ -142,60 +137,41 @@ public class AdoptionDialogController {
errorMsg += "Status is required.\n";
}
//If no errors, attempt DB operation
if (errorMsg.isEmpty()) {
Adoption adoption = collectAdoption();
try {
AdoptionRequest request = new AdoptionRequest();
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
request.setAdoptionDate(dpAdoptionDate.getValue());
request.setAdoptionStatus(cbAdoptionStatus.getValue());
//Try inserting into DB
if (mode.equals("Add")) {
try {
numRow = AdoptionDB.insertAdoption(adoption);
if (mode.equals("Add")) {
AdoptionApi.getInstance().createAdoption(request);
} else {
String[] parts = lblAdoptionId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid adoption ID format");
}
Long adoptionId = Long.parseLong(parts[1]);
AdoptionApi.getInstance().updateAdoption(adoptionId, request);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
"Inserting new adoption record");
throw new RuntimeException(e);
}
}
//Try updating adoption
else {
try {
numRow = AdoptionDB.updateAdoption(adoption.getAdoptionId(), adoption);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
"Updating adoption with ID: " + adoption.getAdoptionId());
throw new RuntimeException(e);
}
}
//If no rows are affected, an issue has occurred
if (numRow == 0) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
//DB operation worked!
else {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
mode + " adoption");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
//If there are errors, display them
else {
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(errorMsg);
@@ -203,30 +179,6 @@ public class AdoptionDialogController {
}
}
//Collects user input, builds an Adoption object
private Adoption collectAdoption() {
int adoptionId = 0;
//Only grab adoption ID if in edit mode
if (lblAdoptionId.isVisible()) {
adoptionId = Integer.parseInt(lblAdoptionId.getText().split(": ")[1]);
}
Pet selectedPet = cbPet.getSelectionModel().getSelectedItem();
Customer selectedCustomer = cbCustomer.getSelectionModel().getSelectedItem();
String date = dpAdoptionDate.getValue().toString();
String status = cbAdoptionStatus.getValue();
return new Adoption(
adoptionId,
selectedPet.getPetId(),
selectedCustomer.getCustomerId(),
selectedCustomer.toString(),
date,
selectedPet.getPetPrice(),
status
);
}
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();
@@ -234,35 +186,35 @@ public class AdoptionDialogController {
stage.close();
}
//Edit mode
//Inserts data into fields
public void displayAdoptionDetails(Adoption adoption) {
if (adoption != null) {
//Displays adoption ID
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
//Select pet
for (Pet pet : cbPet.getItems()) {
if (pet.getPetId() == adoption.getPetId()) {
for (DropdownOption pet : cbPet.getItems()) {
if (pet.getLabel().equals(adoption.getPetName())) {
cbPet.getSelectionModel().select(pet);
break;
}
}
//Select customer
for (Customer customer : cbCustomer.getItems()) {
if (customer.getCustomerId() == adoption.getCustomerId()) {
for (DropdownOption customer : cbCustomer.getItems()) {
if (customer.getLabel().equals(adoption.getCustomerName())) {
cbCustomer.getSelectionModel().select(customer);
break;
}
}
//Select adoption date
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
try {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.displayAdoptionDetails",
e,
"Parsing adoption date");
}
}
//Select adoption status
for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status);
@@ -272,8 +224,6 @@ public class AdoptionDialogController {
}
}
//Sets dialog mode
//Also updates label and adoption ID visibility
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Adoption");

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
@@ -10,11 +11,16 @@ import javafx.stage.Stage;
import javafx.scene.control.ListCell;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.database.*;
import org.example.petshopdesktop.models.*;
import org.example.petshopdesktop.api.dto.appointment.AppointmentRequest;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.Time;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
public class AppointmentDialogController {
@@ -25,9 +31,9 @@ public class AppointmentDialogController {
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<Service> cbService;
@FXML private ComboBox<Customer> cbCustomer;
@FXML private ComboBox<Pet> cbPet;
@FXML private ComboBox<DropdownOption> cbService;
@FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute;
@@ -67,17 +73,33 @@ public class AppointmentDialogController {
@FXML
public void initialize() {
try {
cbService.setItems(ServiceDB.getServices());
cbCustomer.setItems(CustomerDB.getCustomers());
cbPet.setItems(PetDB.getPets());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading combo box data for services, customers, and pets");
e.printStackTrace();
}
new Thread(() -> {
try {
List<DropdownOption> services = DropdownApi.getInstance().getServices();
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
Platform.runLater(() -> {
if (services != null) {
cbService.setItems(FXCollections.observableArrayList(services));
}
if (customers != null) {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
}
if (pets != null) {
cbPet.setItems(FXCollections.observableArrayList(pets));
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading combo box data for services, customers, and pets");
e.printStackTrace();
});
}
}).start();
cbAppointmentStatus.setItems(statusList);
@@ -88,20 +110,49 @@ public class AppointmentDialogController {
cbMinute.getItems().addAll(0, 15, 30, 45);
// Show pet name
cbPet.setCellFactory(param -> new ListCell<>() {
// Show dropdown labels
cbService.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbService.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbPet.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbPet.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
@@ -118,26 +169,40 @@ public class AppointmentDialogController {
selectedAppointment = appt;
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
dpAppointmentDate.setValue(
java.time.LocalDate.parse(appt.getAppointmentDate())
);
try {
dpAppointmentDate.setValue(
java.time.LocalDate.parse(appt.getAppointmentDate())
);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.displayAppointmentDetails",
e,
"Parsing appointment date");
}
cbAppointmentStatus.setValue(appt.getAppointmentStatus());
Time time = Time.valueOf(appt.getAppointmentTime());
cbHour.setValue(time.toLocalTime().getHour());
cbMinute.setValue(time.toLocalTime().getMinute());
try {
LocalTime time = LocalTime.parse(appt.getAppointmentTime());
cbHour.setValue(time.getHour());
cbMinute.setValue(time.getMinute());
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.displayAppointmentDetails",
e,
"Parsing appointment time");
}
cbService.getItems().forEach(s -> {
if (s.getServiceId() == appt.getServiceId()) cbService.setValue(s);
if (s.getId() == appt.getServiceId()) cbService.setValue(s);
});
cbCustomer.getItems().forEach(c -> {
if (c.getCustomerId() == appt.getCustomerId()) cbCustomer.setValue(c);
if (c.getId() == appt.getCustomerId()) cbCustomer.setValue(c);
});
cbPet.getItems().forEach(p -> {
if (p.getPetId() == appt.getPetId()) cbPet.setValue(p);
if (p.getId() == appt.getPetId()) cbPet.setValue(p);
});
}
@@ -159,45 +224,40 @@ public class AppointmentDialogController {
return;
}
Time appointmentTime =
Time.valueOf(String.format(
"%02d:%02d:00",
cbHour.getValue(),
cbMinute.getValue()
));
LocalTime appointmentTime = LocalTime.of(cbHour.getValue(), cbMinute.getValue());
Appointment appt = new Appointment(
selectedAppointment == null ? 0 : selectedAppointment.getAppointmentId(),
cbService.getValue().getServiceId(),
cbCustomer.getValue().getCustomerId(),
dpAppointmentDate.getValue().toString(),
appointmentTime.toString(),
cbAppointmentStatus.getValue()
);
AppointmentRequest request = new AppointmentRequest();
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
request.setCustomerId(cbCustomer.getValue().getId());
request.setServiceId(cbService.getValue().getId());
request.setAppointmentDate(dpAppointmentDate.getValue());
request.setAppointmentTime(appointmentTime);
request.setAppointmentStatus(cbAppointmentStatus.getValue());
try {
new Thread(() -> {
try {
if (mode.equals("Add")) {
AppointmentApi.getInstance().createAppointment(request);
} else {
AppointmentApi.getInstance().updateAppointment(
(long) selectedAppointment.getAppointmentId(),
request
);
}
if (mode.equals("Add")) {
int newId = AppointmentDB.insertAppointment(appt);
AppointmentDB.insertAppointmentPet(newId, cbPet.getValue().getPetId());
} else {
AppointmentDB.updateAppointment(
selectedAppointment.getAppointmentId(),
appt,
cbPet.getValue().getPetId()
);
Platform.runLater(() -> closeStage(e));
} catch (Exception ex) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.buttonSaveClicked",
ex,
"Saving appointment in " + mode + " mode");
ex.printStackTrace();
showError("Error saving appointment: " + ex.getMessage());
});
}
closeStage(e);
} catch (Exception ex) {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.buttonSaveClicked",
ex,
"Saving appointment in " + mode + " mode");
ex.printStackTrace();
showError("Error saving appointment");
}
}).start();
}
//

View File

@@ -1,5 +1,7 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
@@ -11,14 +13,19 @@ import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.inventory.InventoryRequest;
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.endpoints.InventoryApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
public class InventoryDialogController {
@@ -60,12 +67,23 @@ public class InventoryDialogController {
public Product fromString(String string) { return null; }
});
//Load product list from DB into combobox
//Load product list from API into combobox
try {
cbProduct.setItems(ProductDB.getProducts());
}
catch (SQLException e) {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
if (productResponses != null) {
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
}
cbProduct.setItems(products);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryDialogController.initialize",
e,
@@ -106,75 +124,43 @@ public class InventoryDialogController {
//Operation only occurs if there are no errors
if (errorMsg.isEmpty()) {
//Ensures duplicate entries aren't possible
if (mode.equals("Add")) {
try {
InventoryRequest request = new InventoryRequest();
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
request.setProdId((long) selectedProduct.getProdId());
int quantity;
try {
quantity = Integer.parseInt(txtQuantity.getText());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid quantity format");
}
request.setQuantity(quantity);
if (InventoryDB.productExistsInInventory(selectedProduct.getProdId())) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Duplicate Entry");
alert.setContentText("An inventory record for \"" + selectedProduct.getProdName() + "\" already exists.");
alert.showAndWait();
return;
if (mode.equals("Add")) {
InventoryApi.getInstance().createInventory(request);
} else {
String[] parts = lblInventoryId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid inventory ID format");
}
Long inventoryId = Long.parseLong(parts[1]);
InventoryApi.getInstance().updateInventory(inventoryId, request);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryDialogController.buttonSaveClicked",
e,
"Checking if product exists in inventory");
throw new RuntimeException(e);
}
}
Inventory inventory = collectInventory();
//Adding inventory
if (mode.equals("Add")) {
try {
numRow = InventoryDB.insertInventory(inventory);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryDialogController.buttonSaveClicked",
e,
"Inserting new inventory record");
throw new RuntimeException(e);
}
}
//Updating inventory
else {
try {
numRow = InventoryDB.updateInventory(inventory.getInventoryId(), inventory);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryDialogController.buttonSaveClicked",
e,
"Updating inventory with ID: " + inventory.getInventoryId());
throw new RuntimeException(e);
}
}
//Display database operation result
if (numRow == 0) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryDialogController.buttonSaveClicked",
e,
mode + " inventory");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
@@ -187,27 +173,6 @@ public class InventoryDialogController {
}
}
//Create Inventory object using values entered by user
private Inventory collectInventory() {
int inventoryId = 0;
//Grab inventory ID when editing pre-existing record
if (lblInventoryId.isVisible()) {
inventoryId = Integer.parseInt(lblInventoryId.getText().split(": ")[1]);
}
//Get selected product
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
//Build and returns Inventory object
return new Inventory(
inventoryId,
selectedProduct.getProdId(),
selectedProduct.getProdName(),
Integer.parseInt(txtQuantity.getText())
);
}
//Close dialog view
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();

View File

@@ -8,14 +8,15 @@ import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.PetDB;
import org.example.petshopdesktop.models.Category;
import org.example.petshopdesktop.api.dto.pet.PetRequest;
import org.example.petshopdesktop.api.dto.pet.PetResponse;
import org.example.petshopdesktop.api.endpoints.PetApi;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.time.LocalDate;
public class PetDialogController {
@@ -77,7 +78,6 @@ public class PetDialogController {
}
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0;
String errorMsg = "";
//Check validation (input required)
@@ -102,46 +102,34 @@ public class PetDialogController {
errorMsg += Validator.isNonNegativeInteger(txtPetAge.getText(), "Age");
if(errorMsg.isEmpty()){
Pet pet = collectPet();
if(mode.equals("Add")) {
try{
numRow = PetDB.insertPet(pet);
PetRequest request = buildPetRequest();
try {
if(mode.equals("Add")) {
PetApi.getInstance().createPet(request);
} else {
String[] parts = lblPetId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid pet ID format");
}
Long petId = Long.parseLong(parts[1]);
PetApi.getInstance().updatePet(petId, request);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"PetDialogController.buttonSaveClicked",
e,
"Inserting new pet record");
throw new RuntimeException(e);
}
}
else {
try {
numRow = PetDB.updatePet(pet.getPetId(), pet);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"PetDialogController.buttonSaveClicked",
e,
"Updating pet with ID: " + pet.getPetId());
throw new RuntimeException(e);
}
}
//if no rows were affected then there was an error (prompt user of error)
if (numRow == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else {
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"PetDialogController.buttonSaveClicked",
e,
mode + " pet record");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Operation Error");
alert.setContentText(mode + " failed: " + e.getMessage());
alert.showAndWait();
}
}
else{
@@ -152,23 +140,27 @@ public class PetDialogController {
}
}
private Pet collectPet() {
int petId =0;
Pet pet = null;
if(lblPetId.isVisible()){
petId = Integer.parseInt(lblPetId.getText().split(": ")[1]);
private PetRequest buildPetRequest() {
PetRequest request = new PetRequest();
request.setPetName(txtPetName.getText());
request.setPetSpecies(txtPetSpecies.getText());
request.setPetBreed(txtPetBreed.getText());
request.setPetStatus(cbPetStatus.getValue());
try {
request.setPetPrice(new BigDecimal(txtPetPrice.getText()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid price format");
}
pet = new Pet(
petId,
txtPetName.getText(),
txtPetSpecies.getText(),
txtPetBreed.getText(),
Integer.parseInt(txtPetAge.getText()),
cbPetStatus.getValue(),
Double.parseDouble(txtPetPrice.getText())
);
return pet;
int age;
try {
age = Integer.parseInt(txtPetAge.getText());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid age format");
}
LocalDate dateOfBirth = LocalDate.now().minusYears(age);
return request;
}
private void closeStage(MouseEvent mouseEvent) {

View File

@@ -10,15 +10,14 @@ import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.CategoryDB;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.models.Category;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.product.ProductRequest;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.util.List;
public class ProductDialogController {
@@ -29,7 +28,7 @@ public class ProductDialogController {
private Button btnSave;
@FXML
private ComboBox<Category> cbProdCategory;
private ComboBox<DropdownOption> cbProdCategory;
@FXML
private Label lblMode;
@@ -70,16 +69,17 @@ public class ProductDialogController {
//Set up combobox for selecting category
try {
//set up combobox
ObservableList<Category> categories = FXCollections.observableArrayList(); //empty list
categories = CategoryDB.getCategories();
cbProdCategory.setItems(categories);
} catch (SQLException e) {
List<DropdownOption> categories = DropdownApi.getInstance().getCategories();
if (categories != null) {
ObservableList<DropdownOption> categoriesObs = FXCollections.observableArrayList(categories);
cbProdCategory.setItems(categoriesObs);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductDialogController.initialize",
e,
"Loading categories for combo box");
throw new RuntimeException(e);
System.out.println("Error loading categories: " + e.getMessage());
}
}
@@ -108,45 +108,45 @@ public class ProductDialogController {
//Check Validation (format)
errorMsg += Validator.isNonNegativeDouble(txtProdPrice.getText(), "Product Price");
if (errorMsg.isEmpty()) { //no validation errors detected
Product product = collectProduct(); //get product info
if (mode.equals("Add")){ //add mode
try{
numRow = ProductDB.insertProduct(product);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductDialogController.buttonSaveClicked",
e,
"Inserting new product record");
throw new RuntimeException(e);
if (errorMsg.isEmpty()) {
try {
ProductRequest request = new ProductRequest();
request.setProdName(txtProdName.getText());
BigDecimal price;
try {
price = new BigDecimal(txtProdPrice.getText());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid price format");
}
request.setProdPrice(price);
request.setCategoryId(cbProdCategory.getSelectionModel().getSelectedItem().getId());
request.setProdDesc(txtProdDesc.getText());
if (mode.equals("Add")) {
ProductApi.getInstance().createProduct(request);
} else {
String[] parts = lblProdId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid product ID format");
}
Long productId = Long.parseLong(parts[1]);
ProductApi.getInstance().updateProduct(productId, request);
}
}
else { //edit
try{
numRow = ProductDB.updateProduct(product.getProdId(),product);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductDialogController.buttonSaveClicked",
e,
"Updating product with ID: " + product.getProdId());
throw new RuntimeException(e);
}
}
//if no rows were affected then there was an error (prompt user of error)
if (numRow == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else {
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ProductDialogController.buttonSaveClicked",
e,
mode + " product");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
else{ //Display validation errors
@@ -157,28 +157,6 @@ public class ProductDialogController {
}
}
/**
* Collect the product info
* @return product info with the id or the new product
*/
private Product collectProduct(){
int prodId = 0;
Product product = null;
if(lblProdId.isVisible()){ //Edit mode
//get product id from lblId (split the string so we only get the int)
prodId = Integer.parseInt(lblProdId.getText().split(": ")[1]);
}
product = new Product(
prodId,
txtProdName.getText(),
Double.parseDouble(txtProdPrice.getText()),
cbProdCategory.getSelectionModel().getSelectedItem().getCategoryId(),
txtProdDesc.getText()
);
return product;
}
/**
* Display the product data in text fields and combobox
* @param product the product entity containing data to display
@@ -190,10 +168,10 @@ public class ProductDialogController {
txtProdDesc.setText(product.getProdDesc());
txtProdPrice.setText(product.getProdPrice() + "");
//get the right combobox selection
for (Category category : cbProdCategory.getItems()) {
if(category.getCategoryId() == product.getCategoryId()){
for (DropdownOption category : cbProdCategory.getItems()) {
if(category.getLabel().equals(product.getCategoryName())){
cbProdCategory.getSelectionModel().select(category);
break;
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -10,16 +11,14 @@ import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.ProductSupplierDB;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierRequest;
import org.example.petshopdesktop.api.dto.productsupplier.ProductSupplierResponse;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductSupplierApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.math.BigDecimal;
public class ProductSupplierDialogController {
@@ -30,10 +29,10 @@ public class ProductSupplierDialogController {
private Button btnSave;
@FXML
private ComboBox<Product> cbProduct;
private ComboBox<DropdownOption> cbProduct;
@FXML
private ComboBox<Supplier> cbSupplier;
private ComboBox<DropdownOption> cbSupplier;
@FXML
private Label lblMode;
@@ -67,26 +66,78 @@ public class ProductSupplierDialogController {
}
});
//Set up combobox for selecting product and supplier
try{
ObservableList<Supplier> suppliers = FXCollections.observableArrayList(); //empty list
ObservableList<Product> products = FXCollections.observableArrayList(); //empty list
cbSupplier.setButtonCell(new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
cbSupplier.setCellFactory(lv -> new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
//get suppliers and products from DB
suppliers = SupplierDB.getSuppliers();
products = ProductDB.getProducts();
cbProduct.setButtonCell(new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
cbProduct.setCellFactory(lv -> new ListCell<DropdownOption>() {
@Override
protected void updateItem(DropdownOption item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item.getLabel());
}
}
});
//Populate combobox
cbSupplier.setItems(suppliers);
cbProduct.setItems(products);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.initialize",
e,
"Loading suppliers and products for combo boxes");
throw new RuntimeException(e);
}
new Thread(() -> {
try {
var suppliers = DropdownApi.getInstance().getSuppliers();
var products = DropdownApi.getInstance().getProducts();
Platform.runLater(() -> {
if (suppliers != null) {
cbSupplier.setItems(FXCollections.observableArrayList(suppliers));
}
if (products != null) {
cbProduct.setItems(FXCollections.observableArrayList(products));
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.initialize",
e,
"Loading suppliers and products for combo boxes");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Initialization Error");
alert.setContentText("Failed to load dropdown data: " + e.getMessage());
alert.showAndWait();
});
}
}).start();
}
@@ -96,10 +147,8 @@ public class ProductSupplierDialogController {
* @param mouseEvent click event for save button
*/
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRows = 0;
String errorMsg = ""; //error message for validation
String errorMsg = "";
//Check Validation (input required)
errorMsg += Validator.isPresent(txtCost.getText(), "Cost");
if (cbProduct.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Product is required \n";
@@ -108,82 +157,41 @@ public class ProductSupplierDialogController {
errorMsg += "Supplier is required \n";
}
//Check validation (length size)
errorMsg += Validator.isLessThanVarChars(txtCost.getText(), "Cost", 12);
//Check validation (format)
errorMsg += Validator.isNonNegativeDouble(txtCost.getText(), "Cost");
if(errorMsg.isEmpty()){ //no validation errors
ProductSupplier productSupplier = collectProductSupplier(); //get productSupplier info
if (mode.equals("Add")) { //add mode
try{
numRows = ProductSupplierDB.insertProductSupplier(productSupplier);
}
catch(SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Inserting product-supplier (integrity constraint violation)");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText("Add failed \n" +
"the product-supplier link is already in the database");
alert.showAndWait();
numRows = -1; //Update numRow so alert only shows once
closeStage(mouseEvent);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Inserting new product-supplier record");
throw new RuntimeException(e);
}
}
else { //edit
try{
numRows = ProductSupplierDB.updateProductSupplier(selectedSupId, selectedProdId, productSupplier);
}
catch(SQLIntegrityConstraintViolationException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Updating product-supplier (integrity constraint violation) - SupID: " + selectedSupId + ", ProdID: " + selectedProdId);
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText("Edit failed \n" +
"the product-supplier link is already in the database");
alert.showAndWait();
numRows = -1; //Update numRow so alert only shows once
closeStage(mouseEvent);
}
catch(SQLException e){
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
"Updating product-supplier - SupID: " + selectedSupId + ", ProdID: " + selectedProdId);
throw new RuntimeException(e);
}
}
if(errorMsg.isEmpty()){
ProductSupplierRequest request = collectProductSupplierRequest();
//if no rows were affected then there was an error (prompt user of error)
if (numRows == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else if (numRows > 0){
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
}
}
else { //Display validation errors
new Thread(() -> {
try {
if (mode.equals("Add")) {
ProductSupplierApi.getInstance().createProductSupplier(request);
} else {
ProductSupplierApi.getInstance().updateProductSupplier((long) selectedProdId, (long) selectedSupId, request);
}
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"ProductSupplierDialogController.buttonSaveClicked",
e,
mode + " product-supplier");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed: " + e.getMessage());
alert.showAndWait();
});
}
}).start();
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(errorMsg);
@@ -193,18 +201,18 @@ public class ProductSupplierDialogController {
/**
* collect the data for new/updated productSupplier
* @return productSupplier entity with data
* @return productSupplier request with data
*/
private ProductSupplier collectProductSupplier() {
ProductSupplier productSupplier = null;
productSupplier = new ProductSupplier(
cbSupplier.getSelectionModel().getSelectedItem().getSupId(),
cbProduct.getSelectionModel().getSelectedItem().getProdId(),
Double.parseDouble(txtCost.getText())
);
return productSupplier;
private ProductSupplierRequest collectProductSupplierRequest() {
ProductSupplierRequest request = new ProductSupplierRequest();
request.setSupplierId(cbSupplier.getSelectionModel().getSelectedItem().getId());
request.setProductId(cbProduct.getSelectionModel().getSelectedItem().getId());
try {
request.setCost(new BigDecimal(txtCost.getText()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid cost format");
}
return request;
}
/**
@@ -216,20 +224,17 @@ public class ProductSupplierDialogController {
txtCost.setText(productSupplier.getCost() + "");
}
//Get the right combobox selection (product)
for (Product product : cbProduct.getItems()) {
if(product.getProdId() == productSupplier.getProdId()){
for (DropdownOption product : cbProduct.getItems()) {
if(product.getId() == productSupplier.getProdId()){
cbProduct.getSelectionModel().select(product);
}
}
//Get the right combobox selection (supplier)
for (Supplier supplier : cbSupplier.getItems()) {
if (supplier.getSupId() == productSupplier.getSupId()) {
for (DropdownOption supplier : cbSupplier.getItems()) {
if (supplier.getId() == productSupplier.getSupId()) {
cbSupplier.getSelectionModel().select(supplier);
}
}
}
/**

View File

@@ -7,15 +7,19 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.SaleDB;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@@ -31,19 +35,19 @@ public class RefundDialogController {
private Label lblSaleInfo;
@FXML
private TableView<SaleDetail.SaleDetailItem> tvOriginalItems;
private TableView<SaleItemResponse> tvOriginalItems;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, String> colOriginalProduct;
private TableColumn<SaleItemResponse, String> colOriginalProduct;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Integer> colOriginalQuantity;
private TableColumn<SaleItemResponse, Integer> colOriginalQuantity;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalUnitPrice;
private TableColumn<SaleItemResponse, BigDecimal> colOriginalUnitPrice;
@FXML
private TableColumn<SaleDetail.SaleDetailItem, Double> colOriginalTotal;
private TableColumn<SaleItemResponse, BigDecimal> colOriginalTotal;
@FXML
private Button btnAddToRefund;
@@ -78,7 +82,7 @@ public class RefundDialogController {
@FXML
private Button btnCancel;
private SaleDetail currentSale;
private SaleResponse currentSale;
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
@@ -94,7 +98,7 @@ public class RefundDialogController {
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
@@ -113,21 +117,25 @@ public class RefundDialogController {
return;
}
int saleId;
Long saleId;
try {
saleId = Integer.parseInt(saleIdText);
saleId = Long.parseLong(saleIdText);
} catch (NumberFormatException e) {
showError("Load Sale", "Invalid transaction ID.");
return;
}
try {
if (SaleDB.isRefunded(saleId)) {
List<SaleResponse> allSales = SaleApi.getInstance().listSales(0, 1000, null);
boolean alreadyRefunded = allSales.stream()
.anyMatch(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()));
if (alreadyRefunded) {
showError("Load Sale", "This sale has already been refunded.");
return;
}
currentSale = SaleDB.getSaleById(saleId);
currentSale = SaleApi.getInstance().getSale(saleId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
@@ -137,13 +145,13 @@ public class RefundDialogController {
currentSale.getPaymentMethod());
lblSaleInfo.setText(saleInfo);
tvOriginalItems.setItems(currentSale.getItems());
tvOriginalItems.setItems(FXCollections.observableArrayList(currentSale.getItems()));
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
refundItems.clear();
updateRefundTotal();
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
}
@@ -156,14 +164,14 @@ public class RefundDialogController {
return;
}
SaleDetail.SaleDetailItem selected = tvOriginalItems.getSelectionModel().getSelectedItem();
SaleItemResponse selected = tvOriginalItems.getSelectionModel().getSelectedItem();
if (selected == null) {
showError("Add to Refund", "Select an item from the original sale.");
return;
}
int alreadyRefunded = refundItems.stream()
.filter(r -> r.getProdId() == selected.getProdId())
.filter(r -> r.getProdId() == selected.getSaleItemId().intValue())
.mapToInt(RefundItem::getQuantity)
.sum();
@@ -192,10 +200,10 @@ public class RefundDialogController {
}
refundItems.add(new RefundItem(
selected.getProdId(),
selected.getSaleItemId().intValue(),
selected.getProductName(),
quantity,
selected.getUnitPrice()
selected.getUnitPrice().doubleValue()
));
updateRefundTotal();
@@ -226,9 +234,9 @@ public class RefundDialogController {
return;
}
Integer employeeId = UserSession.getInstance().getEmployeeId();
if (employeeId == null || employeeId <= 0) {
showError("Process Refund", "Employee is not set for this account.");
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
showError("Process Refund", "Store is not set for this account.");
return;
}
@@ -249,22 +257,32 @@ public class RefundDialogController {
}
try {
ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
for (RefundItem item : refundItems) {
cartItems.add(new SaleCartItem(item.getProdId(), item.getProductName(), item.getQuantity(), item.getUnitPrice()));
}
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
request.setIsRefund(true);
request.setOriginalSaleId(currentSale.getSaleId());
int refundId = SaleDB.createRefund(currentSale.getSaleId(), employeeId, payment, cartItems);
List<SaleItemRequest> items = new ArrayList<>();
for (RefundItem item : refundItems) {
SaleItemRequest saleItem = new SaleItemRequest();
saleItem.setProdId((long) item.getProdId());
saleItem.setQuantity(-item.getQuantity());
items.add(saleItem);
}
request.setItems(items);
SaleResponse refundResponse = SaleApi.getInstance().createSale(request);
Alert success = new Alert(Alert.AlertType.INFORMATION);
success.setTitle("Refund Processed");
success.setHeaderText(null);
success.setContentText("Refund ID " + refundId + " was created successfully.");
success.setContentText("Refund ID " + refundResponse.getSaleId() + " was created successfully.");
success.showAndWait();
closeDialog();
} catch (SQLException e) {
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
}

View File

@@ -1,18 +1,19 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.DTOs.ServiceDTO;
import org.example.petshopdesktop.api.dto.service.ServiceRequest;
import org.example.petshopdesktop.api.endpoints.ServiceApi;
import org.example.petshopdesktop.util.ActivityLogger;
import javafx.scene.control.Alert;
import javafx.scene.control.ComboBox;
import java.math.BigDecimal;
public class ServiceDialogController {
@@ -45,7 +46,7 @@ public class ServiceDialogController {
private ComboBox<Integer> cbMinutes;
private String mode;
private Service selectedService;
private ServiceDTO selectedService;
@@ -68,7 +69,7 @@ public class ServiceDialogController {
}
}
public void setService(Service service) {
public void setService(ServiceDTO service) {
this.selectedService = service;
lblServiceId.setText("ID: " + service.getServiceId());
@@ -114,22 +115,20 @@ public class ServiceDialogController {
return;
}
int duration = (hours * 60) + minutes;
Service service = new Service(
selectedService == null ? 0 : selectedService.getServiceId(),
name,
desc,
duration,
price
);
try {
int durationMinutes = (hours * 60) + minutes;
ServiceRequest request = new ServiceRequest();
request.setServiceName(name);
request.setServiceDesc(desc);
request.setServicePrice(BigDecimal.valueOf(price));
request.setServiceDuration(durationMinutes);
if (mode.equals("Add")) {
ServiceDB.insertService(service);
ServiceApi.getInstance().createService(request);
} else {
ServiceDB.updateService(selectedService.getServiceId(), service);
Long serviceId = (long) selectedService.getServiceId();
ServiceApi.getInstance().updateService(serviceId, request);
}
close();

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
@@ -8,11 +9,10 @@ import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
public class StaffRegisterDialogController {
@FXML
@@ -49,7 +49,6 @@ public class StaffRegisterDialogController {
String firstName = value(txtFirstName);
String lastName = value(txtLastName);
String email = value(txtEmail);
String phone = value(txtPhone);
String username = value(txtUsername);
String password = txtPassword.getText() == null ? "" : txtPassword.getText();
String confirm = txtPasswordConfirm.getText() == null ? "" : txtPasswordConfirm.getText();
@@ -62,10 +61,6 @@ public class StaffRegisterDialogController {
lblError.setText("Email is required.");
return;
}
if (phone.isBlank()) {
lblError.setText("Phone is required.");
return;
}
if (username.isBlank()) {
lblError.setText("Username is required.");
return;
@@ -79,26 +74,41 @@ public class StaffRegisterDialogController {
return;
}
try {
UserDB.createStaffAccount(firstName, lastName, email, phone, username, password);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Staff Account");
alert.setHeaderText(null);
alert.setContentText("Staff account created. You can log in now.");
alert.showAndWait();
close();
} catch (SQLException e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating staff account");
String msg = e.getMessage() == null ? "Could not create staff account." : e.getMessage();
if (msg.toLowerCase().contains("duplicate") || msg.toLowerCase().contains("unique")) {
lblError.setText("Username already exists.");
} else {
lblError.setText(msg);
btnCreate.setDisable(true);
new Thread(() -> {
try {
UserRequest request = new UserRequest();
request.setUsername(username);
request.setPassword(password);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setRole("STAFF");
request.setActive(true);
UserApi.getInstance().createUser(request);
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Staff Account");
alert.setHeaderText(null);
alert.setContentText("Staff account created. You can log in now.");
alert.showAndWait();
close();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating staff account");
String msg = e.getMessage() == null ? "Could not create staff account." : e.getMessage();
Platform.runLater(() -> {
if (msg.toLowerCase().contains("duplicate") || msg.toLowerCase().contains("unique")) {
lblError.setText("Username already exists.");
} else {
lblError.setText(msg);
}
btnCreate.setDisable(false);
});
}
} catch (RuntimeException e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Database connection");
lblError.setText("Database is not connected.");
}
}).start();
}
@FXML

View File

@@ -10,12 +10,12 @@ import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.api.dto.supplier.SupplierRequest;
import org.example.petshopdesktop.api.dto.supplier.SupplierResponse;
import org.example.petshopdesktop.api.endpoints.SupplierApi;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException;
public class SupplierDialogController {
@FXML
@@ -74,7 +74,6 @@ public class SupplierDialogController {
* @param mouseEvent click event for save button
*/
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0; //how many rows affected
String errorMsg = ""; //error message for validation
//Check validation (input required)
@@ -95,44 +94,33 @@ public class SupplierDialogController {
errorMsg += Validator.isValidPhoneNumber(txtPhone.getText(), "Phone Number");
if(errorMsg.isEmpty()){ //no validation errors detected
Supplier supplier = collectSupplier(); //get supplier info
if (mode.equals("Add")) { //add mode
try{
numRow = SupplierDB.insertSupplier(supplier);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
"Inserting new supplier record");
throw new RuntimeException(e);
SupplierRequest request = createSupplierRequest();
try {
if (mode.equals("Add")) {
SupplierApi.getInstance().createSupplier(request);
} else {
String[] parts = lblSupId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid supplier ID format");
}
Long supplierId = Long.parseLong(parts[1]);
SupplierApi.getInstance().updateSupplier(supplierId, request);
}
}
else{ //edit mode
try{
numRow = SupplierDB.updateSupplier(supplier.getSupId(),supplier);
} catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
"Updating supplier with ID: " + supplier.getSupId());
throw new RuntimeException(e);
}
}
//if no rows were affected then there was an error (prompt user of error)
if (numRow == 0){
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed");
alert.showAndWait();
}
else {
//tell the user operation was successful
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Saved");
alert.setContentText(mode + " succeeded");
alert.showAndWait();
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierDialogController.buttonSaveClicked",
e,
mode.equals("Add") ? "Inserting new supplier record" : "Updating supplier record");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(mode + " failed: " + e.getMessage());
alert.showAndWait();
}
}
else{ //Display validation errors
@@ -154,26 +142,17 @@ public class SupplierDialogController {
}
/**
* Collect the supplier info
* @return supplier info with the id or the new supplier
* Create a supplier request from the form inputs
* @return supplier request for API call
*/
private Supplier collectSupplier(){
int supId = 0;
Supplier supplier = null;
if(lblSupId.isVisible()){ //Edit mode
//get supplier id from lblId (split the string so we only get the int)
supId = Integer.parseInt(lblSupId.getText().split(": ")[1]);
}
supplier = new Supplier(
supId,
txtCompanyName.getText(),
txtContactFirstName.getText(),
txtContactLastName.getText(),
txtEmail.getText(),
txtPhone.getText()
);
return supplier;
private SupplierRequest createSupplierRequest(){
SupplierRequest request = new SupplierRequest();
request.setSupCompany(txtCompanyName.getText());
request.setSupContactFirstName(txtContactFirstName.getText());
request.setSupContactLastName(txtContactLastName.getText());
request.setSupEmail(txtEmail.getText());
request.setSupPhone(txtPhone.getText());
return request;
}
/**

View File

@@ -1,165 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.models.Customer;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class AdoptionDB {
//Select query
private static final String BASE_SELECT =
"SELECT a.adoptionId, a.petId, a.customerId, " +
"CONCAT(c.firstName, ' ', c.lastName) AS customerName, " +
"a.adoptionDate, p.petPrice AS adoptionFee, a.adoptionStatus " +
"FROM adoption a " +
"JOIN customer c ON a.customerId = c.customerId " +
"JOIN pet p ON a.petId = p.petId";
//Retrieve all adoption records from DB
public static ObservableList<Adoption> getAdoptions() throws SQLException {
ObservableList<Adoption> adoptions = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(BASE_SELECT);
//Map results
while (rs.next()) {
adoptions.add(mapRow(rs));
}
conn.close();
return adoptions;
}
//Returns data depending on search query
public static ObservableList<Adoption> getFilteredAdoptions(String filter) throws SQLException {
ObservableList<Adoption> adoptions = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = BASE_SELECT +
" WHERE a.adoptionId LIKE '%" + filter + "%' OR " +
"a.petId LIKE '%" + filter + "%' OR " +
"CONCAT(c.firstName, ' ', c.lastName) LIKE '%" + filter + "%' OR " +
"a.adoptionDate LIKE '%" + filter + "%' OR " +
"p.petPrice LIKE '%" + filter + "%' OR " +
"a.adoptionStatus LIKE '%" + filter + "%'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//Map results
while (rs.next()) {
adoptions.add(mapRow(rs));
}
conn.close();
return adoptions;
}
//Add new adoption
public static int insertAdoption(Adoption adoption) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) VALUES (?, ?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
//Put data in Adoption object
stmt.setInt(1, adoption.getPetId());
stmt.setInt(2, adoption.getCustomerId());
stmt.setString(3, adoption.getAdoptionDate());
stmt.setString(4, adoption.getAdoptionStatus());
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("adoption",
"N/A",
String.format("Adoption record added for Pet ID %d, Customer ID %d", adoption.getPetId(), adoption.getCustomerId()));
}
return numRows;
}
//Updating pre-existing adoption
public static int updateAdoption(int adoptionId, Adoption adoption) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "UPDATE adoption SET petId = ?, customerId = ?, adoptionDate = ?, adoptionStatus = ? WHERE adoptionId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, adoption.getPetId());
stmt.setInt(2, adoption.getCustomerId());
stmt.setString(3, adoption.getAdoptionDate());
stmt.setString(4, adoption.getAdoptionStatus());
stmt.setInt(5, adoptionId);
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("adoption",
String.valueOf(adoptionId),
String.format("Adoption ID %d updated", adoptionId));
}
return numRows;
}
//Delete adoption
public static int deleteAdoption(int adoptionId) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM adoption WHERE adoptionId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, adoptionId);
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("adoption",
String.valueOf(adoptionId),
String.format("Adoption ID %d deleted", adoptionId));
}
return numRows;
}
//Grab list of customers from DB for comboboxes
public static ObservableList<Customer> getCustomers() throws SQLException {
ObservableList<Customer> customers = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT customerId, firstName, lastName, email, phone FROM customer");
while (rs.next()) {
customers.add(new Customer(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5)));
}
conn.close();
return customers;
}
//DRY: converts DB data into usable Java object
private static Adoption mapRow(ResultSet rs) throws SQLException {
return new Adoption(
rs.getInt("adoptionId"),
rs.getInt("petId"),
rs.getInt("customerId"),
rs.getString("customerName"),
rs.getString("adoptionDate"),
rs.getDouble("adoptionFee"),
rs.getString("adoptionStatus")
);
}
}

View File

@@ -1,223 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.models.Appointment;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class AppointmentDB {
// ============================
// GET ALL APPOINTMENTS
// ============================
public static ObservableList<AppointmentDTO> getAppointmentDTOs()
throws SQLException {
ObservableList<AppointmentDTO> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT a.appointmentId,
c.customerId,
CONCAT(c.firstName,' ',c.lastName) AS customerName,
p.petId,
p.petName,
s.serviceId,
s.serviceName,
a.appointmentDate,
a.appointmentTime,
a.appointmentStatus
FROM appointment a
JOIN customer c ON a.customerId = c.customerId
JOIN appointmentPet ap ON a.appointmentId = ap.appointmentId
JOIN pet p ON ap.petId = p.petId
JOIN service s ON a.serviceId = s.serviceId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
AppointmentDTO dto = new AppointmentDTO(
rs.getInt("appointmentId"),
rs.getInt("customerId"),
rs.getString("customerName"),
rs.getInt("petId"),
rs.getString("petName"),
rs.getInt("serviceId"),
rs.getString("serviceName"),
rs.getString("appointmentDate"),
rs.getString("appointmentTime"),
rs.getString("appointmentStatus")
);
list.add(dto);
}
conn.close();
return list;
}
// ============================
// INSERT APPOINTMENT
// ============================
public static int insertAppointment(Appointment appt)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = """
INSERT INTO appointment
(serviceId, customerId, appointmentDate,
appointmentTime, appointmentStatus)
VALUES (?,?,?,?,?)
""";
PreparedStatement ps =
conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, appt.getServiceId());
ps.setInt(2, appt.getCustomerId());
ps.setString(3, appt.getAppointmentDate());
ps.setString(4, appt.getAppointmentTime());
ps.setString(5, appt.getAppointmentStatus());
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
int newId = 0;
if (keys.next()) {
newId = keys.getInt(1);
}
conn.close();
// Log the operation
if (newId > 0) {
ActivityLogger.getInstance().logInsert("appointment",
String.valueOf(newId),
String.format("Appointment created for Customer ID %d, Service ID %d", appt.getCustomerId(), appt.getServiceId()));
}
return newId;
}
//
// LINK PET TO APPOINTMENT
//
public static void insertAppointmentPet(int appointmentId,
int petId)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"INSERT INTO appointmentPet (appointmentId, petId) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, appointmentId);
ps.setInt(2, petId);
int numRows = ps.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("appointmentPet",
String.valueOf(appointmentId),
String.format("Pet ID %d linked to Appointment ID %d", petId, appointmentId));
}
}
//
// UPDATE APPOINTMENT
//
public static int updateAppointment(int id,
Appointment appt,
int petId)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"UPDATE appointment SET serviceId=?, customerId=?, " +
"appointmentDate=?, appointmentTime=?, appointmentStatus=? " +
"WHERE appointmentId=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, appt.getServiceId());
ps.setInt(2, appt.getCustomerId());
ps.setString(3, appt.getAppointmentDate());
ps.setString(4, appt.getAppointmentTime());
ps.setString(5, appt.getAppointmentStatus());
ps.setInt(6, id);
ps.executeUpdate();
String sql2 =
"UPDATE appointmentPet SET petId=? WHERE appointmentId=?";
PreparedStatement ps2 = conn.prepareStatement(sql2);
ps2.setInt(1, petId);
ps2.setInt(2, id);
ps2.executeUpdate();
conn.close();
// Log the operation
ActivityLogger.getInstance().logUpdate("appointment",
String.valueOf(id),
String.format("Appointment ID %d updated", id));
return 1;
}
//
// DELETE APPOINTMENT
//
public static int deleteAppointment(int id)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
PreparedStatement ps1 =
conn.prepareStatement(
"DELETE FROM appointmentPet WHERE appointmentId=?"
);
ps1.setInt(1, id);
ps1.executeUpdate();
PreparedStatement ps2 =
conn.prepareStatement(
"DELETE FROM appointment WHERE appointmentId=?"
);
ps2.setInt(1, id);
int rows = ps2.executeUpdate();
conn.close();
// Log the operation
if (rows > 0) {
ActivityLogger.getInstance().logDelete("appointment",
String.valueOf(id),
String.format("Appointment ID %d deleted", id));
}
return rows;
}
}

View File

@@ -1,41 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Category;
import org.example.petshopdesktop.models.Product;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class CategoryDB {
/**
* gets all the category into an observable list
* @return a list of all the category
* @throws SQLException if failed to find categories in the database
*/
public static ObservableList<Category> getCategories() throws SQLException{
//Connect to the database
ObservableList<Category> categories = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM category");
//While there is still data add categories to the list
while(rs.next()){
Category category = new Category(
rs.getInt(1),
rs.getString(2),
rs.getString(3));
categories.add(category);
}
//close connection and return categories
conn.close();
return categories;
}
}

View File

@@ -1,66 +0,0 @@
package org.example.petshopdesktop.database;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class ConnectionDB {
/**
* Method to try and connect to the database using connectionpetstore.properties.
* @return Connection to the database
*/
public static Connection getConnection(){
String url = "";
String user = "";
String password = "";
Properties prop = new Properties();
Path propsPath;
String explicitPath = System.getenv("PETSTORE_DB_PROPS");
if (explicitPath != null && !explicitPath.isBlank()) {
propsPath = Paths.get(explicitPath);
} else {
Path cwd = Paths.get(System.getProperty("user.dir"), "connectionpetstore.properties");
Path xdg = Paths.get(System.getProperty("user.home"), ".config", "petstore", "connectionpetstore.properties");
Path legacyWindows = Paths.get("./connectionpetstore.properties");
if (Files.exists(cwd)) propsPath = cwd;
else if (Files.exists(xdg)) propsPath = xdg;
else propsPath = legacyWindows;
}
try (FileInputStream fis = new FileInputStream(propsPath.toString())) {
prop.load(fis);
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
}
catch(IOException e){
ActivityLogger.getInstance().logException(
"ConnectionDB.getConnection",
e,
"Reading connection properties file");
throw new RuntimeException("Problem with reading connection info: "+e.getMessage());
}
try{
return DriverManager.getConnection(url,user,password);
}
catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ConnectionDB.getConnection",
e,
"Establishing database connection");
throw new RuntimeException("Problem with database connection: "+e.getMessage());
}
}
}

View File

@@ -1,43 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Customer;
import java.sql.*;
public class CustomerDB {
//
// GET ALL CUSTOMERS
//
public static ObservableList<Customer> getCustomers()
throws SQLException {
ObservableList<Customer> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = "SELECT * FROM customer";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
Customer c = new Customer(
rs.getInt("customerId"),
rs.getString("firstName"),
rs.getString("lastName"),
rs.getString("email"),
rs.getString("phone")
);
list.add(c);
}
conn.close();
return list;
}
}

View File

@@ -1,132 +0,0 @@
package org.example.petshopdesktop.database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class EmployeeDB {
public static int ensureDefaultEmployee(String firstName, String lastName, String email, String phone, String role, boolean isActive) throws SQLException {
Integer existingId = findEmployeeIdByEmail(email);
if (existingId != null) {
return existingId;
}
try (Connection conn = ConnectionDB.getConnection()) {
conn.setAutoCommit(false);
try {
int storeId = getDefaultStoreId(conn);
int employeeId = createEmployee(conn, firstName, lastName, email, phone, role, isActive);
assignEmployeeToStore(conn, employeeId, storeId);
conn.commit();
return employeeId;
} catch (SQLException e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true);
}
}
}
public static Integer findEmployeeIdByEmail(String email) throws SQLException {
String sql = "SELECT employeeId FROM employee WHERE email = ? LIMIT 1";
try (Connection conn = ConnectionDB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, email);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getInt("employeeId");
}
}
}
return null;
}
public static int createEmployee(Connection conn, String firstName, String lastName, String email, String phone, String role, boolean isActive) throws SQLException {
String sql = "INSERT INTO employee (firstName, lastName, email, phone, role, isActive) VALUES (?, ?, ?, ?, ?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, firstName);
ps.setString(2, lastName);
ps.setString(3, email);
ps.setString(4, phone);
ps.setString(5, role);
ps.setBoolean(6, isActive);
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
return keys.getInt(1);
}
}
}
throw new SQLException("Could not create employee.");
}
public static void assignEmployeeToStore(Connection conn, int employeeId, int storeId) throws SQLException {
String sql = "INSERT IGNORE INTO employeeStore (employeeId, storeId) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, employeeId);
ps.setInt(2, storeId);
ps.executeUpdate();
}
}
public static int getDefaultStoreId() throws SQLException {
try (Connection conn = ConnectionDB.getConnection()) {
return getDefaultStoreId(conn);
}
}
public static int getDefaultStoreId(Connection conn) throws SQLException {
Integer existing = firstStoreId(conn);
if (existing != null) {
return existing;
}
String insert = "INSERT INTO storeLocation (storeName, address, phone, email) VALUES ('Main Store', 'N/A', '000-000-0000', 'main@petshop.com')";
try (PreparedStatement ps = conn.prepareStatement(insert, Statement.RETURN_GENERATED_KEYS)) {
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
return keys.getInt(1);
}
}
}
Integer after = firstStoreId(conn);
if (after != null) {
return after;
}
return 1;
}
public static Integer getPrimaryStoreId(int employeeId) throws SQLException {
String sql = "SELECT storeId FROM employeeStore WHERE employeeId = ? ORDER BY storeId ASC LIMIT 1";
try (Connection conn = ConnectionDB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, employeeId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getInt("storeId");
}
}
}
return null;
}
private static Integer firstStoreId(Connection conn) throws SQLException {
String sql = "SELECT storeId FROM storeLocation ORDER BY storeId ASC LIMIT 1";
try (PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getInt("storeId");
}
}
return null;
}
}

View File

@@ -1,158 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class InventoryDB {
//Base selection query
//Returns invID, ProdID, Quantity from Inventory table
//Returns ProdName from Product table
private static final String BASE_SELECT =
"SELECT i.inventoryId, i.prodId, p.prodName, i.quantity " +
"FROM inventory i " +
"JOIN product p ON i.prodId = p.prodId";
//Retrieves inventory records from DB
public static ObservableList<Inventory> getInventory() throws SQLException {
ObservableList<Inventory> inventoryList = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(BASE_SELECT);
while (rs.next()) {
inventoryList.add(mapRow(rs));
}
conn.close();
return inventoryList;
}
//Returns records depending on search
public static ObservableList<Inventory> getFilteredInventory(String filter) throws SQLException {
ObservableList<Inventory> inventoryList = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = BASE_SELECT +
" WHERE i.inventoryId LIKE ? OR " +
"i.prodId LIKE ? OR " +
"p.prodName LIKE ? OR " +
"i.quantity LIKE ?";
String filteredString = "%" + filter + "%";
PreparedStatement stmt = conn.prepareStatement(sql);
for (int i = 1; i <= 4; i++) {
stmt.setString(i, filteredString);
}
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
inventoryList.add(mapRow(rs));
}
conn.close();
return inventoryList;
}
//Checks if the product already has an inventory entry
public static boolean productExistsInInventory(int prodId) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "SELECT COUNT(*) FROM inventory WHERE prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, prodId);
ResultSet rs = stmt.executeQuery();
boolean exists = rs.next() && rs.getInt(1) > 0;
conn.close();
return exists;
}
//Inserting new inventory record
public static int insertInventory(Inventory inventory) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO inventory (prodId, quantity) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, inventory.getProdId());
stmt.setInt(2, inventory.getQuantity());
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("inventory",
"N/A",
String.format("Inventory added for Product ID %d, Quantity %d", inventory.getProdId(), inventory.getQuantity()));
}
return numRows;
}
//Updating inventory record
public static int updateInventory(int inventoryId, Inventory inventory) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "UPDATE inventory SET prodId = ?, quantity = ? WHERE inventoryId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, inventory.getProdId());
stmt.setInt(2, inventory.getQuantity());
stmt.setInt(3, inventoryId);
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("inventory",
String.valueOf(inventoryId),
String.format("Inventory ID %d updated", inventoryId));
}
return numRows;
}
//Deleting inventory record
public static int deleteInventory(int inventoryId) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM inventory WHERE inventoryId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, inventoryId);
int numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("inventory",
String.valueOf(inventoryId),
String.format("Inventory ID %d deleted", inventoryId));
}
return numRows;
}
//DRY: converts DB data into usable Java object
private static Inventory mapRow(ResultSet rs) throws SQLException {
return new Inventory(
rs.getInt("inventoryId"),
rs.getInt("prodId"),
rs.getString("prodName"),
rs.getInt("quantity")
);
}
}

View File

@@ -1,167 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class PetDB {
public static ObservableList<Pet> getPets() throws SQLException {
//Connect to the database
ObservableList<Pet> pets = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM pet");
//While there is still data add pets to the list
while(rs.next()){
Pet pet = new Pet(
rs.getInt(1),
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getInt(5),
rs.getString(6),
rs.getDouble(7)
);
pets.add(pet);
}
//close connection and return pets
conn.close();
return pets;
}
public static ObservableList<Pet> getFilteredPets(String filter) throws SQLException {
//Connect to the database
ObservableList<Pet> pets = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Get SQL query for filtered word
String sql = "SELECT * FROM pet " +
" WHERE " +
"petName LIKE ? OR " +
"petSpecies LIKE ? OR " +
"petBreed LIKE ? OR " +
"petAge LIKE ? OR " +
"petStatus LIKE ? OR " +
"petPrice LIKE ?";
String filteredString = "%" + filter + "%";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, filteredString);
stmt.setString(2, filteredString);
stmt.setString(3, filteredString);
stmt.setString(4, filteredString);
stmt.setString(5, filteredString);
stmt.setString(6, filteredString);
ResultSet rs = stmt.executeQuery();
while(rs.next()){
Pet pet = new Pet(
rs.getInt(1),
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getInt(5),
rs.getString(6),
rs.getDouble(7)
);
pets.add(pet);
}
conn.close();
return pets;
}
public static int insertPet(Pet pet) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO pet (petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice)" +
" VALUES (?, ?, ?, ?, ?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, pet.getPetId());
stmt.setString(2, pet.getPetName());
stmt.setString(3, pet.getPetSpecies());
stmt.setString(4, pet.getPetBreed());
stmt.setInt(5, pet.getPetAge());
stmt.setString(6, pet.getPetStatus());
stmt.setDouble(7, pet.getPetPrice());
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("pet",
String.valueOf(pet.getPetId()),
String.format("Pet '%s' added", pet.getPetName()));
}
return numRows;
}
public static int updatePet(int petId, Pet pet) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "UPDATE pet SET " +
" petName = ?, " +
" petSpecies = ?, " +
" petBreed = ?, " +
" petAge = ?, " +
" petStatus = ?, " +
" petPrice = ? " +
" WHERE petId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, pet.getPetName());
stmt.setString(2, pet.getPetSpecies());
stmt.setString(3, pet.getPetBreed());
stmt.setInt(4, pet.getPetAge());
stmt.setString(5, pet.getPetStatus());
stmt.setDouble(6, pet.getPetPrice());
stmt.setInt(7, petId);
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("pet",
String.valueOf(petId),
String.format("Pet '%s' updated", pet.getPetName()));
}
return numRows;
}
public static int deletePet(int petId) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM pet WHERE petId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, petId);
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("pet",
String.valueOf(petId),
String.format("Pet ID %d deleted", petId));
}
return numRows;
}
}

View File

@@ -1,234 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
/**
* A class containing all the methods relating to CRUD on Products table
*/
public class ProductDB {
/**
* gets all the products into an observable list
* @return a list of all the products
* @throws SQLException if failed to find products in the database
*/
public static ObservableList<Product> getProducts() throws SQLException{
//Connect to the database
ObservableList<Product> products = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM product");
//While there is still data add products to the list
while(rs.next()){
Product product = new Product(
rs.getInt(1),
rs.getString(2),
rs.getDouble(3),
rs.getInt(4),
rs.getString(5));
products.add(product);
}
//close connection and return products
conn.close();
return products;
}
/**
* gets all the ProductDTOs into an observable list for display (displays categoryName instead of categoryId)
* @return the list of all the ProductDTOs
* @throws SQLException if failed to find products in the database
*/
public static ObservableList<ProductDTO> getProductDTO() throws SQLException{
//Connect to the database
ObservableList<ProductDTO> products = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
String sql = "SELECT p.prodId, p.prodName, p.prodPrice, p.categoryId, c.categoryName, p.prodDesc " +
"FROM product p " +
"LEFT JOIN category c ON p.categoryId = c.categoryId";
ResultSet rs = stmt.executeQuery(sql);
//While there is still data add products to the list
while(rs.next()){
ProductDTO product = new ProductDTO(
rs.getInt(1),
rs.getString(2),
rs.getDouble(3),
rs.getInt(4),
rs.getString(5),
rs.getString(6));
products.add(product);
}
//close connection and return products
conn.close();
return products;
}
/**
* Inserts a new product to the database
* @param product product entity to be inserted
* @return number of rows affected in the database
* @throws SQLException if insertion failed
*/
public static int insertProduct(Product product) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO product (prodId, prodName, prodPrice, categoryId, prodDesc)" +
"VALUES (?, ?, ?, ?, ?)";
//These are the values from product to put into the query above
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, product.getProdId());
stmt.setString(2, product.getProdName());
stmt.setDouble(3, product.getProdPrice());
stmt.setInt(4, product.getCategoryId());
stmt.setString(5, product.getProdDesc());
//update the number of rows affected, return and close connection
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("product",
String.valueOf(product.getProdId()),
String.format("Product '%s' added", product.getProdName()));
}
return numRows;
}
/**
* Update an existing product to the database
* @param prodId id of product
* @param product new product data
* @return number of rows affected in the database
* @throws SQLException if update failed
*/
public static int updateProduct(int prodId, Product product) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "UPDATE product SET " +
" prodName = ?, " +
" prodPrice = ?, " +
" categoryId = ?, " +
" prodDesc = ? " +
" WHERE prodId = ?";
//update values to query
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, product.getProdName());
stmt.setDouble(2, product.getProdPrice());
stmt.setInt(3, product.getCategoryId());
stmt.setString(4, product.getProdDesc());
stmt.setInt(5, prodId);
//Update rows and close connection
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("product",
String.valueOf(prodId),
String.format("Product '%s' updated", product.getProdName()));
}
return numRows;
}
/**
* Delete a product from the database
* @param prodId product id to be deleted
* @return number of rows affected in the database
* @throws SQLException if delete failed
*/
public static int deleteProduct(int prodId) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM product WHERE prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, prodId);
//close connection and update rows affected
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("product",
String.valueOf(prodId),
String.format("Product ID %d deleted", prodId));
}
return numRows;
}
/**
* Gets a list of productDTOs that is filtered by a given string
* @param filter the word to filter table
* @return ObservableList of ProductDTOs with the filtered data
* @throws SQLException if getting products failed
*/
public static ObservableList<ProductDTO> getFilteredProductDTOs(String filter) throws SQLException {
//Connect to the database
ObservableList<ProductDTO> products = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Get SQL query for filtered word
String sql =
"SELECT p.prodId, p.prodName, p.prodPrice, p.categoryId, c.categoryName, p.prodDesc" +
" FROM product p" +
" LEFT JOIN category c ON p.categoryId = c.categoryId" +
" WHERE " +
"prodName LIKE ? OR " +
"prodPrice LIKE ? OR " +
"categoryName LIKE ? OR " +
"prodDesc LIKE ?";
//add % wildcard so the query can use LIKE to filter data
String filteredString = "%" + filter + "%";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, filteredString);
stmt.setString(2, filteredString);
stmt.setString(3, filteredString);
stmt.setString(4, filteredString);
//execute query
ResultSet rs = stmt.executeQuery();
//While there is still data add products to the list
while(rs.next()){
ProductDTO product = new ProductDTO(
rs.getInt(1),
rs.getString(2),
rs.getDouble(3),
rs.getInt(4),
rs.getString(5),
rs.getString(6));
products.add(product);
}
conn.close();
return products;
}
}

View File

@@ -1,219 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.DTOs.ProductSupplierDTO;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class ProductSupplierDB {
/**
* gets all the productSupplier into an observable list
* @return a list of all the productSupplierDTOs
* @throws SQLException if failed to find productSupplier in the database
*/
public static ObservableList<ProductSupplierDTO> getProductSupplierDTO() throws SQLException{
//Connect to the database
ObservableList<ProductSupplierDTO> productSuppliers = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
String sql = "SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " +
"FROM productSupplier ps " +
"LEFT JOIN product p " +
"ON p.prodId = ps.prodId " +
"LEFT JOIN supplier s " +
"ON s.supId = ps.supId";
ResultSet rs = stmt.executeQuery(sql);
//While there is still data add productSupplier to list
while(rs.next()){
ProductSupplierDTO productSupplier = new ProductSupplierDTO(
rs.getInt(1),
rs.getInt(2),
rs.getString(3),
rs.getString(4),
rs.getDouble(5)
);
productSuppliers.add(productSupplier);
}
//close connection and return products
conn.close();
return productSuppliers;
}
/**
* Gets a list of ProductSupplierDTOs that is filtered by a given string
* @param filter the word to filter table
* @return ObservableList of ProductSupplierDTOs with the filtered data
* @throws SQLException if getting productSuppliers failed
*/
public static ObservableList<ProductSupplierDTO> getFilteredProductSupplierDTO (String filter) throws SQLException {
//connect to the database
ObservableList<ProductSupplierDTO> productSuppliers = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Get SQL query for filter word
String sql =
"SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " +
"FROM product p " +
"LEFT JOIN productSupplier ps " +
"ON p.prodId = ps.prodId " +
"LEFT JOIN supplier s " +
"ON s.supId = ps.supId " +
"WHERE " +
"prodName LIKE ? OR " +
"supCompany LIKE ? OR " +
"cost LIKE ?";
//add % wildcard so query can use LIKE to filter data
String filteredString = "%" + filter + "%";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, filteredString);
stmt.setString(2, filteredString);
stmt.setString(3, filteredString);
//execute query
ResultSet rs = stmt.executeQuery();
//While there is still data add productSupplier to the list
while(rs.next()){
ProductSupplierDTO productSupplier = new ProductSupplierDTO(
rs.getInt(1),
rs.getInt(2),
rs.getString(3),
rs.getString(4),
rs.getDouble(5)
);
productSuppliers.add(productSupplier);
}
conn.close();
return productSuppliers;
}
/**
* Inserts a new productSupplier to the database
* @param productSupplier productSupplier entity to be inserted
* @return number of rows affected
* @throws SQLException if insert failed
*/
public static int insertProductSupplier(ProductSupplier productSupplier) throws SQLException{
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO productSupplier (prodId, supId, cost) " +
"VALUES (?, ?, ?)";
//These are the values from productSupplier to put into query above
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, productSupplier.getProdId());
stmt.setInt(2, productSupplier.getSupId());
stmt.setDouble(3, productSupplier.getCost());
//update number of rows affected, return and close connection
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("productSupplier",
String.format("ProdID:%d-SupID:%d", productSupplier.getProdId(), productSupplier.getSupId()),
String.format("Product-Supplier relation added for Product ID %d, Supplier ID %d", productSupplier.getProdId(), productSupplier.getSupId()));
}
return numRows;
}
/**
* Update a productSupplier by deleting old productSupplier and inserting new one
* @param oldProdId old product id (used to change primary compound key)
* @param oldSupId old supplier id (used to change primary compound key)
* @param productSupplier productSupplier entity with new info to update (including new primary compound key)
* @return number of rows affected in database
* @throws SQLException if update failed
*/
public static int updateProductSupplier(int oldSupId, int oldProdId, ProductSupplier productSupplier) throws SQLException{
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
//Make transaction so update can be rolled back if insert failed
conn.setAutoCommit(false);
try{
//Delete old data first
String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, oldSupId);
stmt.setInt(2, oldProdId);
numRows = stmt.executeUpdate();
//Then change the data by inserting a new relation with given keys (only if delete worked)
if(numRows > 0){
sql = "INSERT INTO productSupplier (prodId, supId, cost) " +
"VALUES (?, ?, ?)";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, productSupplier.getProdId());
stmt.setInt(2, productSupplier.getSupId());
stmt.setDouble(3, productSupplier.getCost());
numRows = stmt.executeUpdate();
}
//Commit changes if both delete and insert worked
conn.commit();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("productSupplier",
String.format("ProdID:%d-SupID:%d", productSupplier.getProdId(), productSupplier.getSupId()),
String.format("Product-Supplier relation updated from ProdID:%d-SupID:%d to ProdID:%d-SupID:%d", oldProdId, oldSupId, productSupplier.getProdId(), productSupplier.getSupId()));
}
}
catch(SQLException e){
//Rollback CRUD failed
conn.rollback();
throw e;
}
finally {
//Set auto commit back to true before closing connection
conn.setAutoCommit(true);
conn.close();
}
return numRows;
}
/**
* Delete a productSupplier from the database
* @param prodId id of the product
* @param supId id of the supplier
* @return number of rows affected in the database
* @throws SQLException if delete failed
*/
public static int deleteProductSupplier(int supId, int prodId) throws SQLException{
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, supId);
stmt.setInt(2, prodId);
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("productSupplier",
String.format("ProdID:%d-SupID:%d", prodId, supId),
String.format("Product-Supplier relation deleted for Product ID %d, Supplier ID %d", prodId, supId));
}
return numRows;
}
}

View File

@@ -1,45 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import java.sql.*;
public class PurchaseOrderDB {
public static ObservableList<PurchaseOrderDTO> getPurchaseOrders()
throws SQLException {
ObservableList<PurchaseOrderDTO> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT po.purchaseOrderId,
s.supCompany,
po.orderDate,
po.status
FROM purchaseOrder po
JOIN supplier s ON po.supId = s.supId
ORDER BY po.purchaseOrderId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
list.add(new PurchaseOrderDTO(
rs.getInt("purchaseOrderId"),
rs.getString("supCompany"),
rs.getString("orderDate"),
rs.getString("status")
));
}
conn.close();
return list;
}
}

View File

@@ -1,572 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.SaleDTO;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.models.analytics.*;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
import java.time.LocalDate;
public class SaleDB {
/**
* Get all sale items with details
* @return ObservableList of SaleDTOs
* @throws SQLException if database operation fails
*/
public static ObservableList<SaleDTO> getSales() throws SQLException {
ObservableList<SaleDTO> sales = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT
s.saleId,
DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate,
CONCAT(e.firstName, ' ', e.lastName) as employeeName,
p.prodName,
si.quantity,
si.unitPrice,
(si.quantity * si.unitPrice) as lineTotal,
s.paymentMethod
FROM sale s
JOIN saleItem si ON s.saleId = si.saleId
JOIN product p ON si.prodId = p.prodId
JOIN employee e ON s.employeeId = e.employeeId
ORDER BY s.saleDate DESC, s.saleId, si.saleItemId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
sales.add(new SaleDTO(
rs.getInt("saleId"),
rs.getString("saleDate"),
rs.getString("employeeName"),
rs.getString("prodName"),
rs.getInt("quantity"),
rs.getDouble("unitPrice"),
rs.getDouble("lineTotal"),
rs.getString("paymentMethod")
));
}
conn.close();
return sales;
}
/**
* Get filtered sale items
* @param filter search term
* @return ObservableList of SaleDTOs matching the filter
* @throws SQLException if database operation fails
*/
public static ObservableList<SaleDTO> getFilteredSales(String filter) throws SQLException {
ObservableList<SaleDTO> sales = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT
s.saleId,
DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate,
CONCAT(e.firstName, ' ', e.lastName) as employeeName,
p.prodName,
si.quantity,
si.unitPrice,
(si.quantity * si.unitPrice) as lineTotal,
s.paymentMethod
FROM sale s
JOIN saleItem si ON s.saleId = si.saleId
JOIN product p ON si.prodId = p.prodId
JOIN employee e ON s.employeeId = e.employeeId
WHERE s.saleId LIKE ?
OR p.prodName LIKE ?
OR CONCAT(e.firstName, ' ', e.lastName) LIKE ?
OR s.paymentMethod LIKE ?
ORDER BY s.saleDate DESC, s.saleId, si.saleItemId
""";
PreparedStatement pstmt = conn.prepareStatement(sql);
String searchPattern = "%" + filter + "%";
pstmt.setString(1, searchPattern);
pstmt.setString(2, searchPattern);
pstmt.setString(3, searchPattern);
pstmt.setString(4, searchPattern);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
sales.add(new SaleDTO(
rs.getInt("saleId"),
rs.getString("saleDate"),
rs.getString("employeeName"),
rs.getString("prodName"),
rs.getInt("quantity"),
rs.getDouble("unitPrice"),
rs.getDouble("lineTotal"),
rs.getString("paymentMethod")
));
}
conn.close();
return sales;
}
public static ObservableList<SaleLineItem> getSaleLineItems() throws SQLException {
ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT
s.saleId,
DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate,
CONCAT(e.firstName, ' ', e.lastName) as employeeName,
p.prodName,
si.quantity,
si.unitPrice,
(si.quantity * si.unitPrice) as total,
s.paymentMethod,
s.isRefund
FROM sale s
JOIN saleItem si ON s.saleId = si.saleId
JOIN product p ON si.prodId = p.prodId
JOIN employee e ON s.employeeId = e.employeeId
ORDER BY s.saleDate DESC, s.saleId, si.saleItemId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
saleItems.add(new SaleLineItem(
rs.getInt("saleId"),
rs.getString("saleDate"),
rs.getString("employeeName"),
rs.getString("prodName"),
rs.getInt("quantity"),
rs.getDouble("unitPrice"),
rs.getDouble("total"),
rs.getString("paymentMethod"),
rs.getBoolean("isRefund")
));
}
conn.close();
return saleItems;
}
public static int createSale(int employeeId, String paymentMethod, ObservableList<SaleCartItem> cartItems) throws SQLException {
if (cartItems.isEmpty()) {
throw new SQLException("Cannot create sale with empty cart");
}
Connection conn = ConnectionDB.getConnection();
conn.setAutoCommit(false);
try {
double totalAmount = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
String insertSale = """
INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId)
VALUES (NOW(), ?, ?, ?, 1)
""";
PreparedStatement saleStmt = conn.prepareStatement(insertSale, Statement.RETURN_GENERATED_KEYS);
saleStmt.setDouble(1, totalAmount);
saleStmt.setString(2, paymentMethod);
saleStmt.setInt(3, employeeId);
saleStmt.executeUpdate();
ResultSet rs = saleStmt.getGeneratedKeys();
if (!rs.next()) {
throw new SQLException("Failed to get generated sale ID");
}
int saleId = rs.getInt(1);
String insertItem = """
INSERT INTO saleItem (saleId, prodId, quantity, unitPrice)
VALUES (?, ?, ?, ?)
""";
String updateInventory = """
UPDATE inventory
SET quantity = quantity - ?
WHERE prodId = ?
""";
PreparedStatement itemStmt = conn.prepareStatement(insertItem);
PreparedStatement invStmt = conn.prepareStatement(updateInventory);
for (SaleCartItem item : cartItems) {
itemStmt.setInt(1, saleId);
itemStmt.setInt(2, item.getProdId());
itemStmt.setInt(3, item.getQuantity());
itemStmt.setDouble(4, item.getUnitPrice());
itemStmt.executeUpdate();
invStmt.setInt(1, item.getQuantity());
invStmt.setInt(2, item.getProdId());
int updated = invStmt.executeUpdate();
if (updated == 0) {
throw new SQLException("Failed to update inventory for product ID " + item.getProdId());
}
}
conn.commit();
ActivityLogger.getInstance().logInsert("sale",
String.format("Sale ID: %d", saleId),
String.format("Created sale with %d items, total: $%.2f", cartItems.size(), totalAmount));
return saleId;
} catch (SQLException e) {
conn.rollback();
ActivityLogger.getInstance().logException("SaleDB.createSale", e, "Creating sale");
throw e;
} finally {
conn.setAutoCommit(true);
conn.close();
}
}
public static ObservableList<DailySalesData> getDailySalesRevenue() throws SQLException {
ObservableList<DailySalesData> dailySales = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT DATE(s.saleDate) as saleDate, SUM(s.totalAmount) as revenue
FROM sale s
GROUP BY DATE(s.saleDate)
ORDER BY saleDate ASC
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
LocalDate date = rs.getDate("saleDate").toLocalDate();
double revenue = rs.getDouble("revenue");
dailySales.add(new DailySalesData(date, revenue));
}
conn.close();
return dailySales;
}
public static ObservableList<ProductSalesData> getTopProductsByRevenue(int limit) throws SQLException {
ObservableList<ProductSalesData> products = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT p.prodName, SUM(si.quantity * si.unitPrice) as totalRevenue
FROM saleItem si
JOIN product p ON si.prodId = p.prodId
GROUP BY p.prodId, p.prodName
ORDER BY totalRevenue DESC
LIMIT ?
""";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, limit);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String productName = rs.getString("prodName");
double totalRevenue = rs.getDouble("totalRevenue");
products.add(new ProductSalesData(productName, 0, totalRevenue));
}
conn.close();
return products;
}
public static ObservableList<ProductSalesData> getTopProductsByQuantity(int limit) throws SQLException {
ObservableList<ProductSalesData> products = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT p.prodName, SUM(si.quantity) as totalQuantity,
SUM(si.quantity * si.unitPrice) as totalRevenue
FROM saleItem si
JOIN product p ON si.prodId = p.prodId
GROUP BY p.prodId, p.prodName
ORDER BY totalQuantity DESC
LIMIT ?
""";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, limit);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String productName = rs.getString("prodName");
int totalQuantity = rs.getInt("totalQuantity");
double totalRevenue = rs.getDouble("totalRevenue");
products.add(new ProductSalesData(productName, totalQuantity, totalRevenue));
}
conn.close();
return products;
}
public static ObservableList<PaymentMethodData> getPaymentMethodDistribution() throws SQLException {
ObservableList<PaymentMethodData> paymentMethods = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT s.paymentMethod, COUNT(*) as transactionCount,
SUM(s.totalAmount) as totalRevenue
FROM sale s
GROUP BY s.paymentMethod
ORDER BY totalRevenue DESC
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
String paymentMethod = rs.getString("paymentMethod");
int transactionCount = rs.getInt("transactionCount");
double totalRevenue = rs.getDouble("totalRevenue");
paymentMethods.add(new PaymentMethodData(paymentMethod, transactionCount, totalRevenue));
}
conn.close();
return paymentMethods;
}
public static ObservableList<EmployeeSalesData> getEmployeeSalesPerformance() throws SQLException {
ObservableList<EmployeeSalesData> employees = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT CONCAT(e.firstName, ' ', e.lastName) as employeeName,
COUNT(DISTINCT s.saleId) as transactionCount,
SUM(s.totalAmount) as totalRevenue,
COALESCE(SUM(si.quantity), 0) as totalItemsSold
FROM sale s
JOIN employee e ON s.employeeId = e.employeeId
LEFT JOIN saleItem si ON s.saleId = si.saleId
GROUP BY e.employeeId, e.firstName, e.lastName
ORDER BY totalRevenue DESC
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
String employeeName = rs.getString("employeeName");
int transactionCount = rs.getInt("transactionCount");
double totalRevenue = rs.getDouble("totalRevenue");
int totalItemsSold = rs.getInt("totalItemsSold");
employees.add(new EmployeeSalesData(employeeName, transactionCount, totalRevenue, totalItemsSold));
}
conn.close();
return employees;
}
public static SalesSummary getSalesSummary() throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT COUNT(DISTINCT s.saleId) as totalTransactions,
COALESCE(SUM(s.totalAmount), 0) as totalRevenue,
COALESCE(AVG(s.totalAmount), 0) as avgTransactionValue,
COALESCE(SUM(si.quantity), 0) as totalItemsSold
FROM sale s
LEFT JOIN saleItem si ON s.saleId = si.saleId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
SalesSummary summary = null;
if (rs.next()) {
int totalTransactions = rs.getInt("totalTransactions");
double totalRevenue = rs.getDouble("totalRevenue");
double avgTransactionValue = rs.getDouble("avgTransactionValue");
int totalItemsSold = rs.getInt("totalItemsSold");
summary = new SalesSummary(totalTransactions, totalRevenue, avgTransactionValue, totalItemsSold);
}
conn.close();
return summary;
}
public static SaleDetail getSaleById(int saleId) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String saleSql = """
SELECT s.saleId, s.saleDate, s.totalAmount, s.paymentMethod,
CONCAT(e.firstName, ' ', e.lastName) as employeeName,
s.isRefund
FROM sale s
JOIN employee e ON s.employeeId = e.employeeId
WHERE s.saleId = ?
""";
PreparedStatement saleStmt = conn.prepareStatement(saleSql);
saleStmt.setInt(1, saleId);
ResultSet saleRs = saleStmt.executeQuery();
if (!saleRs.next()) {
conn.close();
throw new SQLException("Sale not found with ID: " + saleId);
}
boolean isRefund = saleRs.getBoolean("isRefund");
if (isRefund) {
conn.close();
throw new SQLException("Cannot refund a refund transaction");
}
SaleDetail detail = new SaleDetail(
saleRs.getInt("saleId"),
saleRs.getTimestamp("saleDate").toLocalDateTime(),
saleRs.getDouble("totalAmount"),
saleRs.getString("paymentMethod"),
saleRs.getString("employeeName"),
FXCollections.observableArrayList()
);
String itemsSql = """
SELECT si.prodId, p.prodName, si.quantity, si.unitPrice,
(si.quantity * si.unitPrice) as total
FROM saleItem si
JOIN product p ON si.prodId = p.prodId
WHERE si.saleId = ?
""";
PreparedStatement itemsStmt = conn.prepareStatement(itemsSql);
itemsStmt.setInt(1, saleId);
ResultSet itemsRs = itemsStmt.executeQuery();
while (itemsRs.next()) {
detail.getItems().add(new SaleDetail.SaleDetailItem(
itemsRs.getInt("prodId"),
itemsRs.getString("prodName"),
itemsRs.getInt("quantity"),
itemsRs.getDouble("unitPrice"),
itemsRs.getDouble("total")
));
}
conn.close();
return detail;
}
public static boolean isRefunded(int saleId) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT COUNT(*) as refundCount
FROM sale
WHERE originalSaleId = ? AND isRefund = TRUE
""";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, saleId);
ResultSet rs = pstmt.executeQuery();
boolean refunded = false;
if (rs.next()) {
refunded = rs.getInt("refundCount") > 0;
}
conn.close();
return refunded;
}
public static int createRefund(int originalSaleId, int employeeId, String paymentMethod, ObservableList<SaleCartItem> refundItems) throws SQLException {
if (refundItems.isEmpty()) {
throw new SQLException("Cannot create refund with empty items");
}
Connection conn = ConnectionDB.getConnection();
SaleDetail originalSale = getSaleById(originalSaleId);
conn = ConnectionDB.getConnection();
if (isRefunded(originalSaleId)) {
throw new SQLException("This sale has already been refunded");
}
conn.setAutoCommit(false);
try {
double totalAmount = -refundItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
String insertSale = """
INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, isRefund, originalSaleId)
VALUES (NOW(), ?, ?, ?, 1, TRUE, ?)
""";
PreparedStatement saleStmt = conn.prepareStatement(insertSale, Statement.RETURN_GENERATED_KEYS);
saleStmt.setDouble(1, totalAmount);
saleStmt.setString(2, paymentMethod);
saleStmt.setInt(3, employeeId);
saleStmt.setInt(4, originalSaleId);
saleStmt.executeUpdate();
ResultSet rs = saleStmt.getGeneratedKeys();
if (!rs.next()) {
throw new SQLException("Failed to get generated refund ID");
}
int refundId = rs.getInt(1);
String insertItem = """
INSERT INTO saleItem (saleId, prodId, quantity, unitPrice)
VALUES (?, ?, ?, ?)
""";
String updateInventory = """
UPDATE inventory
SET quantity = quantity + ?
WHERE prodId = ?
""";
PreparedStatement itemStmt = conn.prepareStatement(insertItem);
PreparedStatement invStmt = conn.prepareStatement(updateInventory);
for (SaleCartItem item : refundItems) {
itemStmt.setInt(1, refundId);
itemStmt.setInt(2, item.getProdId());
itemStmt.setInt(3, -item.getQuantity());
itemStmt.setDouble(4, item.getUnitPrice());
itemStmt.executeUpdate();
invStmt.setInt(1, item.getQuantity());
invStmt.setInt(2, item.getProdId());
int updated = invStmt.executeUpdate();
if (updated == 0) {
throw new SQLException("Failed to update inventory for product ID " + item.getProdId());
}
}
conn.commit();
ActivityLogger.getInstance().logInsert("sale",
String.format("Refund ID: %d", refundId),
String.format("Created refund for sale ID %d with %d items, total: $%.2f", originalSaleId, refundItems.size(), Math.abs(totalAmount)));
return refundId;
} catch (SQLException e) {
conn.rollback();
ActivityLogger.getInstance().logException("SaleDB.createRefund", e, "Creating refund");
throw e;
} finally {
conn.setAutoCommit(true);
conn.close();
}
}
}

View File

@@ -1,129 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
public class ServiceDB {
//
// GET ALL SERVICES
//
public static ObservableList<Service> getServices() throws SQLException {
ObservableList<Service> list = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = "SELECT * FROM service";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
Service service = new Service(
rs.getInt("serviceId"),
rs.getString("serviceName"),
rs.getString("serviceDesc"),
rs.getInt("serviceDuration"),
rs.getDouble("servicePrice")
);
list.add(service);
}
conn.close();
return list;
}
//
// INSERT SERVICE
//
public static int insertService(Service service) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) " +
"VALUES (?, ?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, service.getServiceName());
stmt.setString(2, service.getServiceDesc());
stmt.setInt(3, service.getServiceDuration());
stmt.setDouble(4, service.getServicePrice());
int rows = stmt.executeUpdate();
conn.close();
// Log the operation
if (rows > 0) {
ActivityLogger.getInstance().logInsert("service",
"N/A",
String.format("Service '%s' added", service.getServiceName()));
}
return rows;
}
//
// UPDATE SERVICE
//
public static int updateService(int id, Service service) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"UPDATE service SET " +
"serviceName=?, serviceDesc=?, serviceDuration=?, servicePrice=? " +
"WHERE serviceId=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, service.getServiceName());
stmt.setString(2, service.getServiceDesc());
stmt.setInt(3, service.getServiceDuration());
stmt.setDouble(4, service.getServicePrice());
stmt.setInt(5, id);
int rows = stmt.executeUpdate();
conn.close();
// Log the operation
if (rows > 0) {
ActivityLogger.getInstance().logUpdate("service",
String.valueOf(id),
String.format("Service '%s' updated", service.getServiceName()));
}
return rows;
}
//
// DELETE SERVICE
//
public static int deleteService(int id) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM service WHERE serviceId=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, id);
int rows = stmt.executeUpdate();
conn.close();
// Log the operation
if (rows > 0) {
ActivityLogger.getInstance().logDelete("service",
String.valueOf(id),
String.format("Service ID %d deleted", id));
}
return rows;
}
}

View File

@@ -1,202 +0,0 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.*;
/**
* A class containing all the methods relating to CRUD on Suppliers table
*/
public class SupplierDB {
/**
* gets all the suppliers into an observable list
* @return a list of all the suppliers
* @throws SQLException if failed to find suppliers in the database
*/
public static ObservableList<Supplier> getSuppliers() throws SQLException {
//Connect to the database
ObservableList<Supplier> suppliers = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Execute Query
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM supplier");
//While there is still data add suppliers to the list
while(rs.next()){
Supplier supplier = new Supplier(
rs.getInt(1),
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getString(5),
rs.getString(6));
suppliers.add(supplier);
}
conn.close();
return suppliers;
}
/**
* Inserts a new supplier to the database
* @param supplier supplier entity to be inserted
* @return number of rows affected in the database
* @throws SQLException if insertion failed
*/
public static int insertSupplier(Supplier supplier) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "INSERT INTO supplier (supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone)" +
"VALUES (?, ?, ?, ?, ?, ?)";
//These are the values from supplier to put into the query above
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, supplier.getSupId());
stmt.setString(2, supplier.getSupCompany());
stmt.setString(3, supplier.getSupContactFirstName());
stmt.setString(4, supplier.getSupContactLastName());
stmt.setString(5, supplier.getSupEmail());
stmt.setString(6, supplier.getSupPhone());
//update the number of rows affected, return and close connection
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logInsert("supplier",
String.valueOf(supplier.getSupId()),
String.format("Supplier '%s' added", supplier.getSupCompany()));
}
return numRows;
}
/**
* Update an existing supplier to the database
* @param supId id of supplier
* @param supplier new supplier data
* @return number of rows affected in the database
* @throws SQLException if update failed
*/
public static int updateSupplier(int supId, Supplier supplier) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "UPDATE supplier SET " +
" supCompany = ?, " +
" supContactFirstName = ?, " +
" supContactLastName = ?, " +
" supEmail = ?, " +
" supPhone = ? " +
" WHERE supId = ?";
//updated values to update the supplier with the query above
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, supplier.getSupCompany());
stmt.setString(2, supplier.getSupContactFirstName());
stmt.setString(3, supplier.getSupContactLastName());
stmt.setString(4, supplier.getSupEmail());
stmt.setString(5, supplier.getSupPhone());
stmt.setInt(6, supId);
//Update the rows and close connection
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logUpdate("supplier",
String.valueOf(supId),
String.format("Supplier '%s' updated", supplier.getSupCompany()));
}
return numRows;
}
/**
* Delete a supplier form the database
* @param supId supplier id to be deleted
* @return number of rows affected in the database
* @throws SQLException if delete failed
*/
public static int deleteSupplier(int supId) throws SQLException {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM supplier WHERE supId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
//The supplier id to be deleted for the query above
stmt.setInt(1, supId);
//close connection and update rows affected
numRows = stmt.executeUpdate();
conn.close();
// Log the operation
if (numRows > 0) {
ActivityLogger.getInstance().logDelete("supplier",
String.valueOf(supId),
String.format("Supplier ID %d deleted", supId));
}
return numRows;
}
/**
* Gets a list of Suppliers that is filtered by a given string
* @param filter the word to filter table
* @return ObservableList of suppliers with the filtered data
* @throws SQLException if getting suppliers failed
*/
public static ObservableList<Supplier> getFilteredSuppliers(String filter) throws SQLException {
//Connect to the database
ObservableList<Supplier> suppliers = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
//Get SQL query for filtered word
String sql =
"SELECT * FROM supplier" +
" WHERE " +
"supCompany LIKE ? OR " +
"supContactFirstName LIKE ? OR " +
"supContactLastName LIKE ? OR " +
"supEmail LIKE ? OR " +
"supPhone LIKE ?";
//add % wildcard so the query can use LIKE to filter data
String filteredString = "%" + filter + "%";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, filteredString);
stmt.setString(2, filteredString);
stmt.setString(3, filteredString);
stmt.setString(4, filteredString);
stmt.setString(5, filteredString);
//execute query
ResultSet rs = stmt.executeQuery();
//While there is still data add suppliers to the list
while(rs.next()){
Supplier supplier = new Supplier(
rs.getInt(1),
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getString(5),
rs.getString(6));
suppliers.add(supplier);
}
conn.close();
return suppliers;
}
}

View File

@@ -1,198 +0,0 @@
package org.example.petshopdesktop.database;
import org.example.petshopdesktop.auth.Role;
import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.models.User;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class UserDB {
public static User authenticate(String username, String password) throws SQLException {
String sql = "SELECT u.user_id, u.employee_id, u.username, u.role, e.firstName, e.lastName "
+ "FROM users u "
+ "JOIN employee e ON u.employee_id = e.employeeId "
+ "WHERE u.username = ? AND u.password_hash = SHA2(?, 256) AND e.isActive = TRUE";
try (Connection conn = ConnectionDB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, username);
ps.setString(2, password);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
int userId = rs.getInt("user_id");
int employeeId = rs.getInt("employee_id");
String uname = rs.getString("username");
Role role = Role.valueOf(rs.getString("role").toUpperCase());
String firstName = rs.getString("firstName");
String lastName = rs.getString("lastName");
return new User(userId, employeeId, uname, firstName, lastName, role);
}
}
}
return null;
}
public static void initializeTable() throws SQLException {
String createTable = """
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
username VARCHAR(100) NOT NULL UNIQUE,
password_hash CHAR(64) NOT NULL,
role ENUM('ADMIN','STAFF') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (employee_id) REFERENCES employee(employeeId)
)
""";
try (Connection conn = ConnectionDB.getConnection();
Statement st = conn.createStatement()) {
st.executeUpdate(createTable);
}
ensureCompatibleSchema();
int adminEmployeeId = EmployeeDB.ensureDefaultEmployee("John", "Doe", "john@petshop.com", "111-222-3333", "Manager", true);
int staffEmployeeId = EmployeeDB.ensureDefaultEmployee("Sara", "Smith", "sara@petshop.com", "444-555-6666", "Staff", true);
ensureDefaultUser(adminEmployeeId, "admin", "admin123", Role.ADMIN);
ensureDefaultUser(staffEmployeeId, "staff", "staff123", Role.STAFF);
}
public static int createStaffAccount(String firstName, String lastName, String email, String phone, String username, String password) throws SQLException {
if (username == null || username.isBlank()) {
throw new SQLException("Username is required.");
}
if (password == null || password.isBlank()) {
throw new SQLException("Password is required.");
}
int storeId = EmployeeDB.getDefaultStoreId();
try (Connection conn = ConnectionDB.getConnection()) {
conn.setAutoCommit(false);
try {
int employeeId = EmployeeDB.createEmployee(conn, firstName, lastName, email, phone, "Staff", true);
EmployeeDB.assignEmployeeToStore(conn, employeeId, storeId);
String insertUser = "INSERT INTO users (employee_id, username, password_hash, role) VALUES (?, ?, SHA2(?, 256), 'STAFF')";
try (PreparedStatement ps = conn.prepareStatement(insertUser, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, employeeId);
ps.setString(2, username);
ps.setString(3, password);
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
int userId = keys.getInt(1);
conn.commit();
ActivityLogger.getInstance().logInsert("users", String.valueOf(userId), "Created staff account: " + username);
return userId;
}
}
}
conn.rollback();
throw new SQLException("Could not create staff account.");
} catch (SQLException e) {
conn.rollback();
throw e;
} catch (RuntimeException e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true);
}
}
}
public static List<StaffAccount> getStaffAccounts() throws SQLException {
String sql = "SELECT u.user_id, u.username, u.created_at, e.employeeId, e.firstName, e.lastName, e.email, e.phone, e.isActive "
+ "FROM users u "
+ "JOIN employee e ON u.employee_id = e.employeeId "
+ "WHERE u.role = 'STAFF' "
+ "ORDER BY u.created_at DESC";
List<StaffAccount> accounts = new ArrayList<>();
try (Connection conn = ConnectionDB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
accounts.add(new StaffAccount(
rs.getInt("user_id"),
rs.getInt("employeeId"),
rs.getString("username"),
rs.getString("firstName"),
rs.getString("lastName"),
rs.getString("email"),
rs.getString("phone"),
rs.getBoolean("isActive"),
rs.getTimestamp("created_at")
));
}
}
return accounts;
}
private static void ensureCompatibleSchema() throws SQLException {
try (Connection conn = ConnectionDB.getConnection();
Statement st = conn.createStatement()) {
try {
st.executeUpdate("ALTER TABLE users ADD COLUMN employee_id INT NULL");
} catch (SQLException ignored) {
}
try {
st.executeUpdate("ALTER TABLE users ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL");
} catch (SQLException ignored) {
}
try {
st.executeUpdate("ALTER TABLE users ADD CONSTRAINT fk_users_employee FOREIGN KEY (employee_id) REFERENCES employee(employeeId)");
} catch (SQLException ignored) {
}
}
}
private static void ensureDefaultUser(int employeeId, String username, String password, Role role) throws SQLException {
String insert = "INSERT IGNORE INTO users (employee_id, username, password_hash, role) VALUES (?, ?, SHA2(?, 256), ?)";
String updateIfMissingEmployeeId = "UPDATE users SET employee_id = ? WHERE username = ? AND (employee_id IS NULL OR employee_id = 0)";
try (Connection conn = ConnectionDB.getConnection()) {
int rows;
try (PreparedStatement ps = conn.prepareStatement(insert)) {
ps.setInt(1, employeeId);
ps.setString(2, username);
ps.setString(3, password);
ps.setString(4, role.name());
rows = ps.executeUpdate();
}
try (PreparedStatement ps = conn.prepareStatement(updateIfMissingEmployeeId)) {
ps.setInt(1, employeeId);
ps.setString(2, username);
ps.executeUpdate();
}
if (rows > 0) {
ActivityLogger.getInstance().logInsert("users", username, "Default " + role.name().toLowerCase() + " user created");
}
}
}
}

View File

@@ -8,16 +8,17 @@ public class Adoption {
private SimpleIntegerProperty adoptionId;
private SimpleIntegerProperty petId;
private SimpleIntegerProperty customerId;
private SimpleStringProperty petName;
private SimpleStringProperty customerName;
private SimpleStringProperty adoptionDate;
private SimpleDoubleProperty adoptionFee;
private SimpleStringProperty adoptionStatus;
//Constructor
public Adoption(int adoptionId, int petId, int customerId, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
public Adoption(int adoptionId, int petId, int customerId, String petName, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
this.adoptionId = new SimpleIntegerProperty(adoptionId);
this.petId = new SimpleIntegerProperty(petId);
this.customerId = new SimpleIntegerProperty(customerId);
this.petName = new SimpleStringProperty(petName);
this.customerName = new SimpleStringProperty(customerName);
this.adoptionDate = new SimpleStringProperty(adoptionDate);
this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
@@ -42,6 +43,12 @@ public class Adoption {
public SimpleIntegerProperty customerIdProperty() { return customerId; }
public String getPetName() { return petName.get(); }
public void setPetName(String petName) { this.petName.set(petName); }
public SimpleStringProperty petNameProperty() { return petName; }
public String getCustomerName() { return customerName.get(); }
public void setCustomerName(String customerName) { this.customerName.set(customerName); }

View File

@@ -7,14 +7,21 @@ public class Inventory {
private SimpleIntegerProperty inventoryId;
private SimpleIntegerProperty prodId;
private SimpleStringProperty prodName;
private SimpleStringProperty categoryName;
private SimpleIntegerProperty storeId;
private SimpleStringProperty storeName;
private SimpleIntegerProperty quantity;
private SimpleIntegerProperty reorderLevel;
//Constructor
public Inventory(int inventoryId, int prodId, String prodName, int quantity) {
public Inventory(int inventoryId, int prodId, String prodName, String categoryName, int storeId, String storeName, int quantity, int reorderLevel) {
this.inventoryId = new SimpleIntegerProperty(inventoryId);
this.prodId = new SimpleIntegerProperty(prodId);
this.prodName = new SimpleStringProperty(prodName);
this.categoryName = new SimpleStringProperty(categoryName);
this.storeId = new SimpleIntegerProperty(storeId);
this.storeName = new SimpleStringProperty(storeName);
this.quantity = new SimpleIntegerProperty(quantity);
this.reorderLevel = new SimpleIntegerProperty(reorderLevel);
}
public int getInventoryId() { return inventoryId.get(); }
@@ -35,9 +42,33 @@ public class Inventory {
public SimpleStringProperty prodNameProperty() { return prodName; }
public String getCategoryName() { return categoryName.get(); }
public void setCategoryName(String categoryName) { this.categoryName.set(categoryName); }
public SimpleStringProperty categoryNameProperty() { return categoryName; }
public int getStoreId() { return storeId.get(); }
public void setStoreId(int storeId) { this.storeId.set(storeId); }
public SimpleIntegerProperty storeIdProperty() { return storeId; }
public String getStoreName() { return storeName.get(); }
public void setStoreName(String storeName) { this.storeName.set(storeName); }
public SimpleStringProperty storeNameProperty() { return storeName; }
public int getQuantity() { return quantity.get(); }
public void setQuantity(int quantity) { this.quantity.set(quantity); }
public SimpleIntegerProperty quantityProperty() { return quantity; }
public int getReorderLevel() { return reorderLevel.get(); }
public void setReorderLevel(int reorderLevel) { this.reorderLevel.set(reorderLevel); }
public SimpleIntegerProperty reorderLevelProperty() { return reorderLevel; }
}

View File

@@ -1,35 +1,52 @@
package org.example.petshopdesktop.models;
import java.math.BigDecimal;
import java.time.LocalDate;
public class PurchaseOrder {
private int purchaseOrderId;
private int supId;
private String orderDate;
private long purchaseOrderId;
private String supplierName;
private LocalDate orderDate;
private LocalDate expectedDeliveryDate;
private String status;
private BigDecimal totalAmount;
public PurchaseOrder(int purchaseOrderId,
int supId,
String orderDate,
String status) {
public PurchaseOrder(long purchaseOrderId,
String supplierName,
LocalDate orderDate,
LocalDate expectedDeliveryDate,
String status,
BigDecimal totalAmount) {
this.purchaseOrderId = purchaseOrderId;
this.supId = supId;
this.supplierName = supplierName;
this.orderDate = orderDate;
this.expectedDeliveryDate = expectedDeliveryDate;
this.status = status;
this.totalAmount = totalAmount;
}
public int getPurchaseOrderId() {
public long getPurchaseOrderId() {
return purchaseOrderId;
}
public int getSupId() {
return supId;
public String getSupplierName() {
return supplierName;
}
public String getOrderDate() {
public LocalDate getOrderDate() {
return orderDate;
}
public LocalDate getExpectedDeliveryDate() {
return expectedDeliveryDate;
}
public String getStatus() {
return status;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
}

View File

@@ -3,8 +3,8 @@ package org.example.petshopdesktop.models;
import java.sql.Timestamp;
public class StaffAccount {
private final int userId;
private final int employeeId;
private final long userId;
private final long employeeId;
private final String username;
private final String firstName;
private final String lastName;
@@ -13,7 +13,7 @@ public class StaffAccount {
private final boolean active;
private final Timestamp createdAt;
public StaffAccount(int userId, int employeeId, String username, String firstName, String lastName, String email, String phone, boolean active, Timestamp createdAt) {
public StaffAccount(long userId, long employeeId, String username, String firstName, String lastName, String email, String phone, boolean active, Timestamp createdAt) {
this.userId = userId;
this.employeeId = employeeId;
this.username = username;
@@ -25,11 +25,11 @@ public class StaffAccount {
this.createdAt = createdAt;
}
public int getUserId() {
public long getUserId() {
return userId;
}
public int getEmployeeId() {
public long getEmployeeId() {
return employeeId;
}