externalize business constants

This commit is contained in:
2026-04-17 14:43:00 -06:00
parent 80df6116ab
commit d198fb3d42
7 changed files with 58 additions and 29 deletions

View File

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

View File

@@ -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;
}
}

View File

@@ -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<String> 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;

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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");
}

View File

@@ -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:}