From 186da5afb3400b38b755c2cc11523cb739bb618a Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Sun, 8 Mar 2026 17:45:52 +0800 Subject: [PATCH] Bom Supporting Function --- .../fpsms/config/security/SecurityConfig.java | 9 ++- .../service/BomScoreRecalculateService.kt | 79 +++++++++++++++++++ .../master/web/BomScoreRecalcController.kt | 28 +++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/master/service/BomScoreRecalculateService.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/master/web/BomScoreRecalcController.kt diff --git a/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java b/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java index 3f950db..4ad0fcf 100644 --- a/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java +++ b/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java @@ -39,6 +39,10 @@ public class SecurityConfig { "/py/**" }; + public static final String[] CORS_ALLOWED_METHODS = { + "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS" + }; + @Lazy @Autowired private JwtRequestFilter jwtRequestFilter; @@ -70,7 +74,10 @@ public class SecurityConfig { .cors(Customizer.withDefaults()).csrf(csrf -> csrf.disable()) .requestCache(requestCache -> requestCache.disable()) .authorizeHttpRequests( - authRequest -> authRequest.requestMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated()) + authRequest -> authRequest + .requestMatchers(URL_WHITELIST).permitAll() + .requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll() + .anyRequest().authenticated()) .httpBasic(httpBasic -> httpBasic.authenticationEntryPoint( (request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value()))) .sessionManagement( diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/BomScoreRecalculateService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/BomScoreRecalculateService.kt new file mode 100644 index 0000000..87180eb --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/service/BomScoreRecalculateService.kt @@ -0,0 +1,79 @@ +package com.ffii.fpsms.modules.master.service + +import com.ffii.fpsms.modules.master.entity.Bom +import com.ffii.fpsms.modules.master.entity.BomRepository +import com.ffii.fpsms.modules.settings.entity.BomWeightingScoreRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.math.BigDecimal +import java.math.RoundingMode + +@Service +open class BomScoreRecalculateService( + private val bomRepository: BomRepository, + private val bomWeightingScoreRepository: BomWeightingScoreRepository, +) { + + /** + * Recalculate baseScore for all non-deleted BOMs and persist the result. + */ + @Transactional + open fun recalculateAllBaseScores(): Int { + val boms: List = bomRepository.findAllByDeletedIsFalse() + if (boms.isEmpty()) return 0 + + boms.forEach { bom -> + val newScore = calculateBaseScoreForBom(bom) + bom.baseScore = newScore + } + + bomRepository.saveAll(boms) + return boms.size + } + + /** + * Same logic as BomService.calculateBaseScore, + * duplicated to avoid modifying BomService.kt. + */ + private fun calculateBaseScoreForBom(bom: Bom): BigDecimal { + val scale = 2 + val roundingMode = RoundingMode.HALF_UP + var sum = BigDecimal.ZERO.setScale(scale, roundingMode) + + // Score columns: contribution = (extractedScore / range) * weighting * 100 + val scoreColumns: List> = listOf( + "isDark" to (bom.isDark?.toBigDecimal() ?: BigDecimal.ZERO), + "isFloat" to (bom.isFloat?.toBigDecimal() ?: BigDecimal.ZERO), + "isDense" to (bom.isDense?.toBigDecimal() ?: BigDecimal.ZERO), + "allergicSubstances" to (bom.allergicSubstances?.toBigDecimal() ?: BigDecimal.ZERO), + "timeSequence" to (bom.timeSequence?.toBigDecimal() ?: BigDecimal.ZERO), + "complexity" to (bom.complexity?.toBigDecimal() ?: BigDecimal.ZERO), + ) + + for ((code, extractedScore) in scoreColumns) { + val row = bomWeightingScoreRepository.findByCodeAndDeletedFalse(code) ?: continue + val range = (row.range ?: 1).toBigDecimal() + val weighting = row.weighting ?: continue + if (range.compareTo(BigDecimal.ZERO) == 0) continue + + val contribution = extractedScore + .divide(range, scale, roundingMode) + .multiply(weighting) + .multiply(BigDecimal(100)) + .setScale(scale, roundingMode) + sum = sum.add(contribution) + } + + // equipmentConflict: contribution = weighting * 100 only + val equipmentConflictRow = + bomWeightingScoreRepository.findByCodeAndDeletedFalse("equipmentConflict") + if (equipmentConflictRow?.weighting != null) { + val contribution = equipmentConflictRow.weighting!! + .multiply(BigDecimal(100)) + .setScale(scale, roundingMode) + sum = sum.add(contribution) + } + + return sum + } +} diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/BomScoreRecalcController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/BomScoreRecalcController.kt new file mode 100644 index 0000000..3b648b1 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/BomScoreRecalcController.kt @@ -0,0 +1,28 @@ +package com.ffii.fpsms.modules.master.web + +import com.ffii.fpsms.modules.master.service.BomScoreRecalculateService +import org.springframework.web.bind.annotation.CrossOrigin +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +data class BomScoreRecalcResponse( + val updatedCount: Int, +) + +@RestController +@RequestMapping("/bom/scores") +@CrossOrigin(origins = ["*"], allowedHeaders = ["*"]) +class BomScoreRecalcController( + private val bomScoreRecalculateService: BomScoreRecalculateService, +) { + + /** + * Recalculate and persist baseScore for all BOMs using the current weighting configuration. + */ + @PostMapping("/recalculate") + fun recalculateAll(): BomScoreRecalcResponse { + val count = bomScoreRecalculateService.recalculateAllBaseScores() + return BomScoreRecalcResponse(updatedCount = count) + } +}