From ced651db327e6846d211b31c0d3336ee8707b91e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 17 Apr 2026 14:43:00 -0600 Subject: [PATCH] externalize business constants --- .../petshop/backend/BackendApplication.java | 3 +++ .../backend/config/BusinessProperties.java | 25 +++++++++++++++++++ .../backend/service/AppointmentService.java | 11 +++++--- .../petshop/backend/service/CartService.java | 11 ++++---- .../petshop/backend/service/SaleService.java | 17 ++++++------- .../backend/util/BusinessConstants.java | 11 -------- backend/src/main/resources/application.yml | 9 +++++++ 7 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 backend/src/main/java/com/petshop/backend/config/BusinessProperties.java delete mode 100644 backend/src/main/java/com/petshop/backend/util/BusinessConstants.java diff --git a/backend/src/main/java/com/petshop/backend/BackendApplication.java b/backend/src/main/java/com/petshop/backend/BackendApplication.java index 26d18f3c..584a7fd0 100644 --- a/backend/src/main/java/com/petshop/backend/BackendApplication.java +++ b/backend/src/main/java/com/petshop/backend/BackendApplication.java @@ -1,13 +1,16 @@ package com.petshop.backend; +import com.petshop.backend.config.BusinessProperties; import com.petshop.backend.config.FlywayContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableConfigurationProperties(BusinessProperties.class) @EnableScheduling @EnableAsync @EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) diff --git a/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java new file mode 100644 index 00000000..6d1cee18 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/config/BusinessProperties.java @@ -0,0 +1,25 @@ +package com.petshop.backend.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.math.BigDecimal; +import java.time.LocalTime; + +@ConfigurationProperties(prefix = "petshop.business") +public record BusinessProperties( + LocalTime openTime, + LocalTime closeTime, + int slotIntervalMinutes, + long maxImageSizeBytes, + BigDecimal employeeDiscountPercent, + int loyaltyPointsPerDollar +) { + public BusinessProperties { + if (openTime == null) openTime = LocalTime.of(9, 0); + if (closeTime == null) closeTime = LocalTime.of(17, 0); + if (slotIntervalMinutes <= 0) slotIntervalMinutes = 30; + if (maxImageSizeBytes <= 0) maxImageSizeBytes = 5 * 1024 * 1024; + if (employeeDiscountPercent == null) employeeDiscountPercent = new BigDecimal("0.10"); + if (loyaltyPointsPerDollar <= 0) loyaltyPointsPerDollar = 20; + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java index 59f290a8..34e6da18 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -19,6 +19,7 @@ import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.ServiceRepository; import com.petshop.backend.repository.StoreRepository; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.config.BusinessProperties; import com.petshop.backend.util.AuthenticationHelper; import com.petshop.backend.util.StringUtils; import org.springframework.context.ApplicationEventPublisher; @@ -47,8 +48,9 @@ public class AppointmentService { private final UserRepository userRepository; private final AdoptionRepository adoptionRepository; private final ApplicationEventPublisher eventPublisher; + private final BusinessProperties businessProperties; - public AppointmentService(AppointmentRepository appointmentRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, AdoptionRepository adoptionRepository, ApplicationEventPublisher eventPublisher) { + public AppointmentService(AppointmentRepository appointmentRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, AdoptionRepository adoptionRepository, ApplicationEventPublisher eventPublisher, BusinessProperties businessProperties) { this.appointmentRepository = appointmentRepository; this.serviceRepository = serviceRepository; this.petRepository = petRepository; @@ -56,6 +58,7 @@ public class AppointmentService { this.userRepository = userRepository; this.adoptionRepository = adoptionRepository; this.eventPublisher = eventPublisher; + this.businessProperties = businessProperties; } @Transactional(readOnly = true) @@ -240,8 +243,8 @@ public class AppointmentService { .collect(Collectors.groupingBy(a -> a.getEmployee().getId())); List availableSlots = new ArrayList<>(); - LocalTime startTime = LocalTime.of(9, 0); - LocalTime endTime = LocalTime.of(17, 0); + LocalTime startTime = businessProperties.openTime(); + LocalTime endTime = businessProperties.closeTime(); LocalTime latestStart = endTime.minusMinutes(service.getServiceDuration()); LocalTime currentTime = startTime; @@ -255,7 +258,7 @@ public class AppointmentService { if (anyEmployeeAvailable) { availableSlots.add(currentTime.toString()); } - currentTime = currentTime.plusMinutes(30); + currentTime = currentTime.plusMinutes(businessProperties.slotIntervalMinutes()); } return availableSlots; diff --git a/backend/src/main/java/com/petshop/backend/service/CartService.java b/backend/src/main/java/com/petshop/backend/service/CartService.java index 47e411e6..305d5ef4 100644 --- a/backend/src/main/java/com/petshop/backend/service/CartService.java +++ b/backend/src/main/java/com/petshop/backend/service/CartService.java @@ -7,7 +7,7 @@ import com.petshop.backend.entity.*; import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.*; -import com.petshop.backend.util.BusinessConstants; +import com.petshop.backend.config.BusinessProperties; import com.stripe.Stripe; import com.stripe.exception.StripeException; import com.stripe.model.PaymentIntent; @@ -25,8 +25,6 @@ import java.util.List; @Service public class CartService { - private static final int LOYALTY_POINTS_PER_DOLLAR = BusinessConstants.LOYALTY_POINTS_PER_DOLLAR; - private final CartRepository cartRepository; private final CartItemRepository cartItemRepository; private final UserRepository userRepository; @@ -36,6 +34,7 @@ public class CartService { private final CouponService couponService; private final SaleRepository saleRepository; private final SaleService saleService; + private final BusinessProperties businessProperties; @Value("${stripe.secret-key:}") private String stripeSecretKey; @@ -48,7 +47,8 @@ public class CartService { CouponRepository couponRepository, CouponService couponService, SaleRepository saleRepository, - SaleService saleService) { + SaleService saleService, + BusinessProperties businessProperties) { this.cartRepository = cartRepository; this.cartItemRepository = cartItemRepository; this.userRepository = userRepository; @@ -58,6 +58,7 @@ public class CartService { this.couponService = couponService; this.saleRepository = saleRepository; this.saleService = saleService; + this.businessProperties = businessProperties; } @PostConstruct @@ -473,7 +474,7 @@ public class CartService { } int availablePoints = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0; - int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR; + int wholeDollars = availablePoints / businessProperties.loyaltyPointsPerDollar(); if (wholeDollars <= 0) { return BigDecimal.ZERO; } diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index adf6ab3b..f23796e7 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -8,7 +8,7 @@ import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.event.SaleReceiptEvent; import com.petshop.backend.repository.*; import com.petshop.backend.util.AuthenticationHelper; -import com.petshop.backend.util.BusinessConstants; +import com.petshop.backend.config.BusinessProperties; import com.petshop.backend.util.StringUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; @@ -25,9 +25,6 @@ import java.util.List; @Service public class SaleService { - private static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = BusinessConstants.EMPLOYEE_DISCOUNT_PERCENT; - private static final int LOYALTY_POINTS_PER_DOLLAR = BusinessConstants.LOYALTY_POINTS_PER_DOLLAR; - private final SaleRepository saleRepository; private final ProductRepository productRepository; private final StoreRepository storeRepository; @@ -37,8 +34,9 @@ public class SaleService { private final CouponService couponService; private final CartRepository cartRepository; private final ApplicationEventPublisher eventPublisher; + private final BusinessProperties businessProperties; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository, CouponRepository couponRepository, CouponService couponService, CartRepository cartRepository, ApplicationEventPublisher eventPublisher) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository, CouponRepository couponRepository, CouponService couponService, CartRepository cartRepository, ApplicationEventPublisher eventPublisher, BusinessProperties businessProperties) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; @@ -48,6 +46,7 @@ public class SaleService { this.couponService = couponService; this.cartRepository = cartRepository; this.eventPublisher = eventPublisher; + this.businessProperties = businessProperties; } @Transactional(readOnly = true) @@ -251,7 +250,7 @@ public class SaleService { int pointsDeducted; if (request.getPointsUsed() != null && request.getPointsUsed() > 0) { loyaltyDiscount = BigDecimal.valueOf(request.getPointsUsed()) - .divide(BigDecimal.valueOf(LOYALTY_POINTS_PER_DOLLAR), 2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(businessProperties.loyaltyPointsPerDollar()), 2, RoundingMode.HALF_UP) .min(remainingAfterDiscounts.max(BigDecimal.ZERO)) .setScale(2, RoundingMode.HALF_UP); pointsDeducted = request.getPointsUsed(); @@ -292,7 +291,7 @@ public class SaleService { } if (customer.getRole() == User.Role.STAFF || customer.getRole() == User.Role.ADMIN) { - return remainingAmount.multiply(EMPLOYEE_DISCOUNT_PERCENT).setScale(2, RoundingMode.HALF_UP); + return remainingAmount.multiply(businessProperties.employeeDiscountPercent()).setScale(2, RoundingMode.HALF_UP); } return BigDecimal.ZERO; @@ -304,7 +303,7 @@ public class SaleService { } int availablePoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0; - int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR; + int wholeDollars = availablePoints / businessProperties.loyaltyPointsPerDollar(); if (wholeDollars <= 0) { return BigDecimal.ZERO; } @@ -319,7 +318,7 @@ public class SaleService { if (loyaltyDiscount == null || loyaltyDiscount.compareTo(BigDecimal.ZERO) <= 0) { return 0; } - return loyaltyDiscount.setScale(0, RoundingMode.DOWN).intValue() * LOYALTY_POINTS_PER_DOLLAR; + return loyaltyDiscount.setScale(0, RoundingMode.DOWN).intValue() * businessProperties.loyaltyPointsPerDollar(); } private User resolveWebsiteSaleEmployee(Long storeId) { diff --git a/backend/src/main/java/com/petshop/backend/util/BusinessConstants.java b/backend/src/main/java/com/petshop/backend/util/BusinessConstants.java deleted file mode 100644 index 0be56b8a..00000000 --- a/backend/src/main/java/com/petshop/backend/util/BusinessConstants.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.petshop.backend.util; - -import java.math.BigDecimal; - -public final class BusinessConstants { - - private BusinessConstants() {} - - public static final int LOYALTY_POINTS_PER_DOLLAR = 20; - public static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = new BigDecimal("0.10"); -} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index e3256221..20773f86 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -61,6 +61,15 @@ app: frontend-url: ${FRONTEND_URL:http://localhost:3000} allowed-origins: ${ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000,https://petshop-web.nicepond-c7280126.westus2.azurecontainerapps.io} +petshop: + business: + open-time: "09:00" + close-time: "17:00" + slot-interval-minutes: 30 + max-image-size-bytes: 5242880 + employee-discount-percent: 0.10 + loyalty-points-per-dollar: 20 + azure: storage: connection-string: ${AZURE_STORAGE_CONNECTION_STRING:}