Student: Jingyuan Wang
Student ID: 012394928
C. Customize the HTML user interface for your customer's application. The user interface should include the shop name, the product names, and the names of the parts.
CHANGE - mainscreen.html
Line 14 - <title>My Bicycle Shop</title>
TO
Line 14 - <title>Jingyuan Wang's Computer Supply Shop</title>
CHANGE - mainscreen.html
Line 19 - <h1>Shop</h1>
TO
Line 21-23 - <h1 class="shop-title">🚴 Welcome to My Computer Supply Shop 🚴</h1>
<p class="shop-subtitle">Browse and manage all parts & products below</p>CHANGE - mainscreen.html
Line 10 - (No custom CSS reference)
TO
Line 12 - <link rel="stylesheet" href="/css/demo.css"> <!-- Added custom CSS -->
CHANGE - mainscreen.html
Line 15 - <body>
TO
Line 15 - <body class="shop-body"> <!-- Add body styling -->
CHANGE - mainscreen.html
Line 17 - <div class="container">
TO
Line 17 - <div class="container mt-4">
Line 18-21 - <div class="shop-header text-center mb-4"> <!-- Styled header -->
(Header content with styling classes)
</div>CHANGE - mainscreen.html
Line 24-30 - Basic form layout without Bootstrap flex classes
TO
Line 26-32 - <form th:action="@{/mainscreen}" class="d-flex mb-3 filter-form">
(Updated with Bootstrap flex and margin classes)
CHANGE - mainscreen.html
Line 35-36 - <a th:href="@{/showFormAddInPart}" class="btn btn-primary btn-sm mb-3">Add Inhouse Part</a>
<a th:href="@{/showFormAddOutPart}" class="btn btn-primary btn-sm mb-3">Add Outsourced Part</a>
TO
Line 38-41 - <a th:href="@{/showFormAddInPart}" class="btn btn-info btn-sm me-2">Add Inhouse Part</a>
<a th:href="@{/showFormAddOutPart}" class="btn btn-info btn-sm">Add Outsourced Part</a>CHANGE - mainscreen.html
Line 37 - <table class="table table-bordered table-striped">
TO
Line 43 - <table class="table table-hover table-striped part-table">
CHANGE - mainscreen.html
Line 38 - <thead class="thead-dark">
TO
Line 44 - <thead class="table-dark">CHANGE - mainscreen.html
Line 22 - <h2>Parts</h2>
TO
Line 24 - <h2 class="section-title">Parts</h2>
CHANGE - mainscreen.html
Line 56 - <h2>Products</h2>
TO
Line 62 - <h2 class="section-title">Products</h2>CHANGE - mainscreen.html
Line 50-52 - <a th:href="@{/showPartFormForUpdate(partID=${tempPart.id})}" class="btn btn-primary btn-sm mb-3">Update</a>
<a th:href="@{/deletepart(partID=${tempPart.id})}" class="btn btn-primary btn-sm mb-3">Delete</a>
TO
Line 56-58 - <a th:href="@{/showPartFormForUpdate(partID=${tempPart.id})}" class="btn btn-warning btn-sm me-2">Update</a>
<a th:href="@{/deletepart(partID=${tempPart.id})}" class="btn btn-danger btn-sm">Delete</a>ADDED - demo.css (New File)
Custom stylesheet with the following styling:
.shop-body {
background-color: #f4f8fc;
font-family: "Segoe UI", sans-serif;
}
.shop-header {
background-color: #e3f2fd;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.shop-title {
font-size: 2.5rem;
font-weight: bold;
color: #1976d2;
}
.shop-subtitle {
font-size: 1.1rem;
color: #555;
}
.section-title {
margin-top: 2rem;
color: #0d6efd;
border-bottom: 2px solid #0d6efd;
padding-bottom: 4px;
}
.part-table, .product-table {
font-size: 0.95rem;
}
.filter-form input[type="text"] {
min-width: 300px;
}D. Add an "About" page to the application to describe your chosen customer's company to web viewers and include navigation to and from the "About" page and the main screen.
<!-- File: about.html -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>About Us - Jingyuan Wang's Computer Supply Store</title>
<link rel="stylesheet" href="/css/demo.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body class="shop-body">
<div class="container mt-4">
<div class="shop-header text-center mb-4">
<h1 class="shop-title">About Jingyuan Wang's Computer Supply Store</h1>
</div>
<p class="lead">
Jingyuan Wang's Computer Supply Store is your one-stop destination for high-quality computer components and accessories.
Whether you're a professional IT technician, a student building your first PC, or a company upgrading equipment—
we've got you covered. From motherboards and graphics cards to in-house support and custom builds, we deliver excellence and service.
</p>
<p>
Founded in 2025, our mission has always been to make computing easier, faster, and more affordable for everyone.
We proudly support both local customers and nationwide shipping.
</p>
<a href="/" class="btn btn-primary mt-3">← Back to Shop</a>
</div>
</body>
</html>Line 106-110
<!-- Add inside <div class="shop-header text-center mb-4"> -->
<p class="shop-subtitle">
Browse and manage all parts & products below |
<a href="/about" class="link-primary text-decoration-none">About Us</a>
</p>Line 55-58
@GetMapping("/about")
public String showAboutPage() {
return "about";
}E. Add a sample inventory appropriate for your chosen store to the application. You should have five parts and five products in your sample inventory and should not overwrite existing data in the database.
if (partRepository.count() == 0 && productRepository.count() == 0) {
OutsourcedPart processor = new OutsourcedPart();
processor.setCompanyName("Intel Corp");
processor.setName("Intel Core i7 Processor");
processor.setInv(25);
processor.setPrice(349.99);
outsourcedPartRepository.save(processor);
OutsourcedPart memory = new OutsourcedPart();
memory.setCompanyName("Corsair");
memory.setName("16GB DDR4 RAM");
memory.setInv(40);
memory.setPrice(89.99);
outsourcedPartRepository.save(memory);
OutsourcedPart storage = new OutsourcedPart();
storage.setCompanyName("Samsung");
storage.setName("1TB SSD Drive");
storage.setInv(30);
storage.setPrice(129.99);
outsourcedPartRepository.save(storage);
OutsourcedPart graphics = new OutsourcedPart();
graphics.setCompanyName("NVIDIA");
graphics.setName("GeForce RTX 3060");
graphics.setInv(15);
graphics.setPrice(299.99);
outsourcedPartRepository.save(graphics);
OutsourcedPart motherboard = new OutsourcedPart();
motherboard.setCompanyName("ASUS");
motherboard.setName("Gaming Motherboard");
motherboard.setInv(20);
motherboard.setPrice(179.99);
outsourcedPartRepository.save(motherboard);
List<OutsourcedPart> existingParts = (List<OutsourcedPart>) outsourcedPartRepository.findAll();
boolean duplicateFound = false;
for (OutsourcedPart part : existingParts) {
if (part.getName().equals("16GB DDR4 RAM") && part.getCompanyName().equals("Corsair")) {
duplicateFound = true;
break;
}
}
if (duplicateFound) {
OutsourcedPart memoryMultiPack = new OutsourcedPart();
memoryMultiPack.setCompanyName("Corsair");
memoryMultiPack.setName("32GB DDR4 RAM Multi-Pack (2x16GB)");
memoryMultiPack.setInv(20);
memoryMultiPack.setPrice(169.99);
outsourcedPartRepository.save(memoryMultiPack);
}
Product gamingPC = new Product("Gaming Desktop Computer", 1299.99, 8);
productRepository.save(gamingPC);
Product officePC = new Product("Office Desktop Computer", 799.99, 12);
productRepository.save(officePC);
Product laptop = new Product("Business Laptop", 999.99, 15);
productRepository.save(laptop);
Product monitor = new Product("27-inch 4K Monitor", 349.99, 20);
productRepository.save(monitor);
Product keyboard = new Product("Mechanical Gaming Keyboard", 129.99, 35);
productRepository.save(keyboard);
List<Product> existingProducts = (List<Product>) productRepository.findAll();
boolean productDuplicateFound = false;
for (Product product : existingProducts) {
if (product.getName().equals("Mechanical Gaming Keyboard")) {
productDuplicateFound = true;
break;
}
}
if (productDuplicateFound) {
Product keyboardMultiPack = new Product("Gaming Keyboard & Mouse Combo Pack", 199.99, 15);
productRepository.save(keyboardMultiPack);
}
System.out.println("Sample inventory added successfully!");
}F. Add a "Buy Now" button to your product list. Your "Buy Now" button must meet each of the following parameters:
INSERT - MainScreen.html, Line 100-101
<a th:href="@{/buyproduct(productID=${tempProduct.id})}" class="btn btn-success btn-sm"
onclick="if(!(confirm('Are you sure you want to purchase this product?')))return false">Buy Now</a>• The button should decrement the inventory of that product by one. It should not affect the inventory of any of the associated parts.
INSERT - AddProductController.java, Line 176-187
@GetMapping("/buyproduct")
public String buyProduct(@RequestParam("productID") int theId, Model theModel) {
ProductService productService = context.getBean(ProductServiceImpl.class);
Product product = productService.findById(theId);
if (product.getInv() > 0) {
product.setInv(product.getInv() - 1);
productService.save(product);
return "confirmationbuysuccess";
} else {
return "confirmationbuyfailure";
}
}<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="refresh"
content="3;URL='mainscreen'">
<meta charset="UTF-8">
<title>Purchase Successful</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="alert alert-success text-center" role="alert">
<h1 class="alert-heading">✅ Purchase Successful!</h1>
<p>Your product has been purchased successfully. The inventory has been updated.</p>
<hr>
<p class="mb-0">You will be redirected to the main screen in 3 seconds...</p>
</div>
<div class="text-center">
<a href="/mainscreen" class="btn btn-primary">← Back to Main Screen</a>
</div>
</div>
</div>
</div>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="refresh"
content="3;URL='mainscreen'">
<meta charset="UTF-8">
<title>Purchase Failed</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="alert alert-danger text-center" role="alert">
<h1 class="alert-heading">❌ Purchase Failed!</h1>
<p>Sorry, this product is currently out of stock. The purchase could not be completed.</p>
<hr>
<p class="mb-0">You will be redirected to the main screen in 3 seconds...</p>
</div>
<div class="text-center">
<a href="/mainscreen" class="btn btn-primary">← Back to Main Screen</a>
</div>
</div>
</div>
</div>
</body>
</html>INSERT - Part.java, LINES 29-31
@Min(value = 0, message = "Minimum inventory must be >= 0")
int minimum;
@Min(value = 0, message = "Maximum inventory must be >= 0")
int maximum;
INSERT - Part.java, LINES 45-47
public Part(String name, double price, int inv, int minimum, int maximum) {
this.name = name;
this.price = price;
this.inv = inv;
this.minimum = minimum;
this.maximum = maximum;
}
INSERT - Part.java, LINES 110-119
public int getMinimum() {
return minimum;
}
public void setMinimum(int minimum) {
this.minimum = minimum;
}
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}INSERT - InhousePart.java, LINES 17-20
public InhousePart() {
this.minimum = 0;
this.maximum = 100;
}
INSERT - OutsourcedPart.java, LINES 17-20
public OutsourcedPart() {
this.minimum = 0;
this.maximum = 100;
}
MODIFY - BootStrapData.java, LINES 36-80
OutsourcedPart processor = new OutsourcedPart();
processor.setCompanyName("Intel Corp");
processor.setName("Intel Core i7 Processor");
processor.setInv(25);
processor.setPrice(349.99);
processor.setMinimum(5);
processor.setMaximum(50);
outsourcedPartRepository.save(processor);
OutsourcedPart memory = new OutsourcedPart();
memory.setCompanyName("Corsair");
memory.setName("16GB DDR4 RAM");
memory.setInv(40);
memory.setPrice(89.99);
memory.setMinimum(10);
memory.setMaximum(100);
outsourcedPartRepository.save(memory);
OutsourcedPart storage = new OutsourcedPart();
storage.setCompanyName("Samsung");
storage.setName("1TB SSD Drive");
storage.setInv(30);
storage.setPrice(129.99);
storage.setMinimum(5);
storage.setMaximum(75);
outsourcedPartRepository.save(storage);
OutsourcedPart graphics = new OutsourcedPart();
graphics.setCompanyName("NVIDIA");
graphics.setName("GeForce RTX 3060");
graphics.setInv(15);
graphics.setPrice(299.99);
graphics.setMinimum(3);
graphics.setMaximum(25);
outsourcedPartRepository.save(graphics);
OutsourcedPart motherboard = new OutsourcedPart();
motherboard.setCompanyName("ASUS");
motherboard.setName("Gaming Motherboard");
motherboard.setInv(20);
motherboard.setPrice(179.99);
motherboard.setMinimum(5);
motherboard.setMaximum(40);
outsourcedPartRepository.save(motherboard);• Add to the InhousePartForm and OutsourcedPartForm forms additional text inputs for the inventory so the user can set the maximum and minimum values.
INSERT - mainscreen.html, LINES 38-39
<th>Minimum</th>
<th>Maximum</th>
INSERT - mainscreen.html, LINES 48-49
<td th:text="${tempPart.minimum}">1</td>
<td th:text="${tempPart.maximum}">1</td>
INSERT - InhousePartForm.html, LINES 22-24
<p><input type="text" th:field="*{minimum}" placeholder="Minimum Inventory" class="form-control mb-4 col-4"/></p>
<p th:if="${#fields.hasErrors('minimum')}" th:errors="*{minimum}">Minimum Error</p>
<p><input type="text" th:field="*{maximum}" placeholder="Maximum Inventory" class="form-control mb-4 col-4"/></p>
<p th:if="${#fields.hasErrors('maximum')}" th:errors="*{maximum}">Maximum Error</p>
INSERT - OutsourcedPartForm.html, LINES 22-24
<p><input type="text" th:field="*{minimum}" placeholder="Minimum Inventory" class="form-control mb-4 col-4"/></p>
<p th:if="${#fields.hasErrors('minimum')}" th:errors="*{minimum}">Minimum Error</p>
<p><input type="text" th:field="*{maximum}" placeholder="Maximum Inventory" class="form-control mb-4 col-4"/></p>
<p th:if="${#fields.hasErrors('maximum')}" th:errors="*{maximum}">Maximum Error</p>CHANGE - application.properties, LINE 6
FROM: spring.datasource.url=jdbc:h2:file:~/spring-boot-h2-db102
TO: spring.datasource.url=jdbc:h2:file:~/spring-boot-h2-db-inventoryINSERT - Part.java, LINES 121-127
public void validateLimits() {
if (this.inv < this.minimum) {
this.inv = this.minimum;
} else if (this.inv > this.maximum) {
this.inv = this.maximum;
}
}
MODIFY - InhousePartServiceImpl.java, LINE 54
public void save(InhousePart thePart) {
thePart.validateLimits();
partRepository.save(thePart);
}
MODIFY - OutsourcedPartServiceImpl.java, LINE 54
public void save(OutsourcedPart thePart) {
thePart.validateLimits();
partRepository.save(thePart);
}
MODIFY - PartServiceImpl.java, LINE 54
public void save(Part thePart) {
thePart.validateLimits();
partRepository.save(thePart);
}H. Add validation for between or at the maximum and minimum fields. The validation must include the following:
• Display error messages for low inventory when adding and updating parts if the inventory is less than the minimum number of parts.
CREATE - ValidInventoryRange.java (new annotation)
@Constraint(validatedBy = {InventoryRangeValidator.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidInventoryRange {
String message() default "Inventory must be between minimum and maximum values";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
CREATE - InventoryRangeValidator.java (new validator)
Validates that inventory is between minimum and maximum values
MODIFY - Part.java, ADD @ValidInventoryRange annotation to class• Display error messages for low inventory when adding and updating products lowers the part inventory below the minimum.
CREATE - ValidPartInventoryAfterProductUpdate.java (new annotation)
Validates that part inventory won't go below minimum when product inventory increases
CREATE - PartInventoryAfterProductUpdateValidator.java (new validator)
Checks if increasing product inventory would cause part inventory to go below minimum
MODIFY - Product.java, ADD @ValidPartInventoryAfterProductUpdate annotation to class• Display error messages when adding and updating parts if the inventory is greater than the maximum.
MODIFY - InventoryRangeValidator.java
Include validation for inventory exceeding maximum in the same validatorI. Add at least two unit tests for the maximum and minimum fields to the PartTest class in the test package.
MODIFY - PartTest.java, ADD the following test methods after existing tests:
TEST 1 - testSetAndGetMinimum() (LINES 110-121)
• Tests minimum field setter and getter for both InhousePart and OutsourcedPart
• Verifies minimum values can be set and retrieved correctly
• Tests different minimum values for each part type
TEST 2 - testSetAndGetMaximum() (LINES 128-139)
• Tests maximum field setter and getter for both InhousePart and OutsourcedPart
• Verifies maximum values can be set and retrieved correctly
• Tests different maximum values for each part type
TEST 3 - testValidateLimitsEnforcesMinimum() (LINES 146-159)
• Tests validateLimits() method when inventory is below minimum
• Verifies inventory gets adjusted to minimum value for both part types
• Uses different minimum values to ensure proper functionality
TEST 4 - testValidateLimitsEnforcesMaximum() (LINES 166-179)
• Tests validateLimits() method when inventory is above maximum
• Verifies inventory gets adjusted to maximum value for both part types
• Uses different maximum values to ensure proper functionality
TEST 5 - testValidateLimitsWithinBounds() (LINES 186-199)
• Tests validateLimits() method when inventory is within valid range
• Verifies inventory remains unchanged when valid for both part types
• Ensures method doesn't modify valid inventory values
TEST 6 - testValidateLimitsAtMinimumBoundary() (LINES 206-219)
• Tests boundary condition when inventory exactly equals minimum
• Verifies edge case handling for both InhousePart and OutsourcedPart
• Ensures inventory stays at minimum when already at boundary
TEST 7 - testValidateLimitsAtMaximumBoundary() (LINES 226-239)
• Tests boundary condition when inventory exactly equals maximum
• Verifies edge case handling for both InhousePart and OutsourcedPart
• Ensures inventory stays at maximum when already at boundary
TEST 8 - testMinimumCanBeZero() (LINES 246-253)
• Tests that minimum can be set to zero for both part types
• Verifies edge case for minimum boundary value
• Ensures no artificial lower limits on minimum fieldNO FILES TO DELETE - All validators are currently in use:
✓ DeletePartValidator.java - USED by @ValidDeletePart annotation on Part.java
✓ EnufPartsValidator.java - USED by @ValidEnufParts annotation on Product.java
✓ PriceProductValidator.java - USED by @ValidProductPrice annotation on Product.java
✓ InventoryRangeValidator.java - USED by @ValidInventoryRange annotation on Part.java
✓ PartInventoryAfterProductUpdateValidator.java - USED by @ValidPartInventoryAfterProductUpdate annotation on Product.java
✓ ValidDeletePart.java - USED on Part class
✓ ValidEnufParts.java - USED on Product class
✓ ValidProductPrice.java - USED on Product class
✓ ValidInventoryRange.java - USED on Part class
✓ ValidPartInventoryAfterProductUpdate.java - USED on Product class
CONCLUSION: No unused validator files found. All validators are actively used in the application.