From b4dd453afb9f4c45fe73322a016dc892c027caa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B2=85?= <907037276@qq.com> Date: Tue, 25 Aug 2026 16:18:36 +0800 Subject: [PATCH] feat: complete product pricing training steps --- .../stu/StudentTrainingAnswerController.java | 90 +++++++++++++++++ .../ProductPricingStepFiveMethodResult.java | 19 ++++ ...oductPricingStepFiveValidationRequest.java | 16 +++ ...roductPricingStepFiveValidationResult.java | 22 +++++ .../ProductPricingStepFourPricePoints.java | 16 +++ .../dto/ProductPricingStepFourPsmRow.java | 25 +++++ ...oductPricingStepFourValidationRequest.java | 13 +++ ...roductPricingStepFourValidationResult.java | 19 ++++ .../dto/ProductPricingStepOneChannel.java | 16 +++ ...roductPricingStepOneValidationRequest.java | 14 +++ ...ProductPricingStepOneValidationResult.java | 20 ++++ ...ductPricingStepThreeValidationRequest.java | 14 +++ ...oductPricingStepThreeValidationResult.java | 26 +++++ .../dto/ProductPricingStepThreeValueItem.java | 16 +++ .../dto/ProductPricingStepTwoCompetitor.java | 22 +++++ .../dto/ProductPricingStepTwoDecision.java | 22 +++++ ...roductPricingStepTwoValidationRequest.java | 17 ++++ ...ProductPricingStepTwoValidationResult.java | 19 ++++ .../ProductPricingStepFiveService.java | 9 ++ .../ProductPricingStepFourService.java | 9 ++ .../service/ProductPricingStepOneService.java | 9 ++ .../ProductPricingStepThreeService.java | 9 ++ .../service/ProductPricingStepTwoService.java | 9 ++ ...pportunitySelectionStepOneServiceImpl.java | 2 +- .../ProductPricingStepFiveServiceImpl.java | 84 ++++++++++++++++ .../ProductPricingStepFourServiceImpl.java | 93 ++++++++++++++++++ .../ProductPricingStepOneServiceImpl.java | 88 +++++++++++++++++ .../ProductPricingStepThreeServiceImpl.java | 80 +++++++++++++++ .../ProductPricingStepTwoServiceImpl.java | 97 +++++++++++++++++++ ...unitySelectionStepFourServiceImplTest.java | 2 +- ...tunitySelectionStepOneServiceImplTest.java | 4 +- ...nitySelectionStepThreeServiceImplTest.java | 2 +- ...ProductPricingStepFiveServiceImplTest.java | 55 +++++++++++ ...ProductPricingStepFourServiceImplTest.java | 71 ++++++++++++++ .../ProductPricingStepOneServiceImplTest.java | 70 +++++++++++++ ...roductPricingStepThreeServiceImplTest.java | 59 +++++++++++ .../ProductPricingStepTwoServiceImplTest.java | 69 +++++++++++++ 37 files changed, 1222 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveMethodResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPricePoints.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPsmRow.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneChannel.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValueItem.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoCompetitor.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoDecision.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFiveService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFourService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepOneService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepThreeService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepTwoService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImpl.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImpl.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImpl.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImpl.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImpl.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImplTest.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImplTest.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImplTest.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImplTest.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImplTest.java diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java index 8022995..bdd7709 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -84,6 +84,16 @@ import com.sztzjy.linkCommerce.entity.dto.ProductValuePositioningStepFourValidat import com.sztzjy.linkCommerce.entity.dto.ProductValuePositioningStepFourValidationResult; import com.sztzjy.linkCommerce.entity.dto.HardwareCostStepOneValidationRequest; import com.sztzjy.linkCommerce.entity.dto.HardwareCostStepOneValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationResult; import com.sztzjy.linkCommerce.entity.dto.HardwareCostStepTwoValidationRequest; import com.sztzjy.linkCommerce.entity.dto.HardwareCostStepTwoValidationResult; import com.sztzjy.linkCommerce.entity.dto.HardwareCostStepThreeValidationRequest; @@ -167,6 +177,11 @@ import com.sztzjy.linkCommerce.service.ProductValuePositioningStepTwoService; import com.sztzjy.linkCommerce.service.ProductValuePositioningStepThreeService; import com.sztzjy.linkCommerce.service.ProductValuePositioningStepFourService; import com.sztzjy.linkCommerce.service.HardwareCostStepOneService; +import com.sztzjy.linkCommerce.service.ProductPricingStepOneService; +import com.sztzjy.linkCommerce.service.ProductPricingStepTwoService; +import com.sztzjy.linkCommerce.service.ProductPricingStepThreeService; +import com.sztzjy.linkCommerce.service.ProductPricingStepFourService; +import com.sztzjy.linkCommerce.service.ProductPricingStepFiveService; import com.sztzjy.linkCommerce.service.HardwareCostStepTwoService; import com.sztzjy.linkCommerce.service.HardwareCostStepThreeService; import com.sztzjy.linkCommerce.service.HardwareCostStepFourService; @@ -330,6 +345,21 @@ public class StudentTrainingAnswerController { @Autowired private HardwareCostStepOneService hardwareCostStepOneService; + @Autowired + private ProductPricingStepOneService productPricingStepOneService; + + @Autowired + private ProductPricingStepTwoService productPricingStepTwoService; + + @Autowired + private ProductPricingStepThreeService productPricingStepThreeService; + + @Autowired + private ProductPricingStepFourService productPricingStepFourService; + + @Autowired + private ProductPricingStepFiveService productPricingStepFiveService; + @Autowired private HardwareCostStepTwoService hardwareCostStepTwoService; @@ -1109,6 +1139,66 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/product-pricing/step-1/validate") + @ApiOperation("新产品定价第一步校验") + public ResultEntity validateProductPricingStepOne( + @RequestBody ProductPricingStepOneValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", + productPricingStepOneService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/product-pricing/step-2/validate") + @ApiOperation("新产品定价第二步校验") + public ResultEntity validateProductPricingStepTwo( + @RequestBody ProductPricingStepTwoValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", + productPricingStepTwoService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/product-pricing/step-3/validate") + @ApiOperation("新产品定价第三步校验") + public ResultEntity validateProductPricingStepThree( + @RequestBody ProductPricingStepThreeValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", + productPricingStepThreeService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/product-pricing/step-4/validate") + @ApiOperation("新产品定价第四步校验") + public ResultEntity validateProductPricingStepFour( + @RequestBody ProductPricingStepFourValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", + productPricingStepFourService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/product-pricing/step-5/validate") + @ApiOperation("新产品定价第五步校验") + public ResultEntity validateProductPricingStepFive( + @RequestBody ProductPricingStepFiveValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", + productPricingStepFiveService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + @PostMapping("/hardware-cost/step-2/validate") @ApiOperation("智能硬件成本估算第二步校验") public ResultEntity validateHardwareCostStepTwo( diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveMethodResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveMethodResult.java new file mode 100644 index 0000000..89268e8 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveMethodResult.java @@ -0,0 +1,19 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepFiveMethodResult { + private String key; + private String methodName; + private BigDecimal measuredPrice; + private BigDecimal grossMarginRate; + + public String getKey() { return key; } + public void setKey(String key) { this.key = key; } + public String getMethodName() { return methodName; } + public void setMethodName(String methodName) { this.methodName = methodName; } + public BigDecimal getMeasuredPrice() { return measuredPrice; } + public void setMeasuredPrice(BigDecimal measuredPrice) { this.measuredPrice = measuredPrice; } + public BigDecimal getGrossMarginRate() { return grossMarginRate; } + public void setGrossMarginRate(BigDecimal grossMarginRate) { this.grossMarginRate = grossMarginRate; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationRequest.java new file mode 100644 index 0000000..504b3f2 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationRequest.java @@ -0,0 +1,16 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductPricingStepFiveValidationRequest { + private String selectedCostMarkupChannel; + private List methodResults; + private String finalAnalysis; + + public String getSelectedCostMarkupChannel() { return selectedCostMarkupChannel; } + public void setSelectedCostMarkupChannel(String selectedCostMarkupChannel) { this.selectedCostMarkupChannel = selectedCostMarkupChannel; } + public List getMethodResults() { return methodResults; } + public void setMethodResults(List methodResults) { this.methodResults = methodResults; } + public String getFinalAnalysis() { return finalAnalysis; } + public void setFinalAnalysis(String finalAnalysis) { this.finalAnalysis = finalAnalysis; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationResult.java new file mode 100644 index 0000000..6149922 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFiveValidationResult.java @@ -0,0 +1,22 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductPricingStepFiveValidationResult { + private boolean valid; + private String message; + private String selectedCostMarkupChannel; + private List methodResults; + private String finalAnalysis; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public String getSelectedCostMarkupChannel() { return selectedCostMarkupChannel; } + public void setSelectedCostMarkupChannel(String selectedCostMarkupChannel) { this.selectedCostMarkupChannel = selectedCostMarkupChannel; } + public List getMethodResults() { return methodResults; } + public void setMethodResults(List methodResults) { this.methodResults = methodResults; } + public String getFinalAnalysis() { return finalAnalysis; } + public void setFinalAnalysis(String finalAnalysis) { this.finalAnalysis = finalAnalysis; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPricePoints.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPricePoints.java new file mode 100644 index 0000000..85443c1 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPricePoints.java @@ -0,0 +1,16 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepFourPricePoints { + private BigDecimal optimalPrice; + private BigDecimal acceptablePrice; + private String acceptableRange; + + public BigDecimal getOptimalPrice() { return optimalPrice; } + public void setOptimalPrice(BigDecimal optimalPrice) { this.optimalPrice = optimalPrice; } + public BigDecimal getAcceptablePrice() { return acceptablePrice; } + public void setAcceptablePrice(BigDecimal acceptablePrice) { this.acceptablePrice = acceptablePrice; } + public String getAcceptableRange() { return acceptableRange; } + public void setAcceptableRange(String acceptableRange) { this.acceptableRange = acceptableRange; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPsmRow.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPsmRow.java new file mode 100644 index 0000000..65b336e --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourPsmRow.java @@ -0,0 +1,25 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepFourPsmRow { + private String id; + private String priceRange; + private BigDecimal tooExpensive; + private BigDecimal tooCheap; + private BigDecimal expensiveAcceptable; + private BigDecimal cheapValue; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getPriceRange() { return priceRange; } + public void setPriceRange(String priceRange) { this.priceRange = priceRange; } + public BigDecimal getTooExpensive() { return tooExpensive; } + public void setTooExpensive(BigDecimal tooExpensive) { this.tooExpensive = tooExpensive; } + public BigDecimal getTooCheap() { return tooCheap; } + public void setTooCheap(BigDecimal tooCheap) { this.tooCheap = tooCheap; } + public BigDecimal getExpensiveAcceptable() { return expensiveAcceptable; } + public void setExpensiveAcceptable(BigDecimal expensiveAcceptable) { this.expensiveAcceptable = expensiveAcceptable; } + public BigDecimal getCheapValue() { return cheapValue; } + public void setCheapValue(BigDecimal cheapValue) { this.cheapValue = cheapValue; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationRequest.java new file mode 100644 index 0000000..28f9bfa --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationRequest.java @@ -0,0 +1,13 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductPricingStepFourValidationRequest { + private List psmRows; + private ProductPricingStepFourPricePoints pricePoints; + + public List getPsmRows() { return psmRows; } + public void setPsmRows(List psmRows) { this.psmRows = psmRows; } + public ProductPricingStepFourPricePoints getPricePoints() { return pricePoints; } + public void setPricePoints(ProductPricingStepFourPricePoints pricePoints) { this.pricePoints = pricePoints; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationResult.java new file mode 100644 index 0000000..3fb0fed --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepFourValidationResult.java @@ -0,0 +1,19 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductPricingStepFourValidationResult { + private boolean valid; + private String message; + private List psmRows; + private ProductPricingStepFourPricePoints pricePoints; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public List getPsmRows() { return psmRows; } + public void setPsmRows(List psmRows) { this.psmRows = psmRows; } + public ProductPricingStepFourPricePoints getPricePoints() { return pricePoints; } + public void setPricePoints(ProductPricingStepFourPricePoints pricePoints) { this.pricePoints = pricePoints; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneChannel.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneChannel.java new file mode 100644 index 0000000..a1c1860 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneChannel.java @@ -0,0 +1,16 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepOneChannel { + private String id; + private BigDecimal markupRate; + private BigDecimal salePrice; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public BigDecimal getMarkupRate() { return markupRate; } + public void setMarkupRate(BigDecimal markupRate) { this.markupRate = markupRate; } + public BigDecimal getSalePrice() { return salePrice; } + public void setSalePrice(BigDecimal salePrice) { this.salePrice = salePrice; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationRequest.java new file mode 100644 index 0000000..2117589 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationRequest.java @@ -0,0 +1,14 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class ProductPricingStepOneValidationRequest { + private BigDecimal unitCost; + private List pricingRows; + + public BigDecimal getUnitCost() { return unitCost; } + public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; } + public List getPricingRows() { return pricingRows; } + public void setPricingRows(List pricingRows) { this.pricingRows = pricingRows; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationResult.java new file mode 100644 index 0000000..88671cf --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepOneValidationResult.java @@ -0,0 +1,20 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class ProductPricingStepOneValidationResult { + private boolean valid; + private String message; + private BigDecimal unitCost; + private List pricingRows; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public BigDecimal getUnitCost() { return unitCost; } + public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; } + public List getPricingRows() { return pricingRows; } + public void setPricingRows(List pricingRows) { this.pricingRows = pricingRows; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationRequest.java new file mode 100644 index 0000000..95e952b --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationRequest.java @@ -0,0 +1,14 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class ProductPricingStepThreeValidationRequest { + private List valueItems; + private BigDecimal discountRate; + + public List getValueItems() { return valueItems; } + public void setValueItems(List valueItems) { this.valueItems = valueItems; } + public BigDecimal getDiscountRate() { return discountRate; } + public void setDiscountRate(BigDecimal discountRate) { this.discountRate = discountRate; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationResult.java new file mode 100644 index 0000000..310951d --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValidationResult.java @@ -0,0 +1,26 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class ProductPricingStepThreeValidationResult { + private boolean valid; + private String message; + private List valueItems; + private BigDecimal perceivedValueTotal; + private BigDecimal discountRate; + private BigDecimal valueBasedPrice; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public List getValueItems() { return valueItems; } + public void setValueItems(List valueItems) { this.valueItems = valueItems; } + public BigDecimal getPerceivedValueTotal() { return perceivedValueTotal; } + public void setPerceivedValueTotal(BigDecimal perceivedValueTotal) { this.perceivedValueTotal = perceivedValueTotal; } + public BigDecimal getDiscountRate() { return discountRate; } + public void setDiscountRate(BigDecimal discountRate) { this.discountRate = discountRate; } + public BigDecimal getValueBasedPrice() { return valueBasedPrice; } + public void setValueBasedPrice(BigDecimal valueBasedPrice) { this.valueBasedPrice = valueBasedPrice; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValueItem.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValueItem.java new file mode 100644 index 0000000..5e403e4 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepThreeValueItem.java @@ -0,0 +1,16 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepThreeValueItem { + private String id; + private String name; + private BigDecimal perceivedValue; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public BigDecimal getPerceivedValue() { return perceivedValue; } + public void setPerceivedValue(BigDecimal perceivedValue) { this.perceivedValue = perceivedValue; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoCompetitor.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoCompetitor.java new file mode 100644 index 0000000..a7ecaf3 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoCompetitor.java @@ -0,0 +1,22 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepTwoCompetitor { + private String id; + private String productType; + private String brand; + private BigDecimal ecommercePrice; + private String differentiator; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getProductType() { return productType; } + public void setProductType(String productType) { this.productType = productType; } + public String getBrand() { return brand; } + public void setBrand(String brand) { this.brand = brand; } + public BigDecimal getEcommercePrice() { return ecommercePrice; } + public void setEcommercePrice(BigDecimal ecommercePrice) { this.ecommercePrice = ecommercePrice; } + public String getDifferentiator() { return differentiator; } + public void setDifferentiator(String differentiator) { this.differentiator = differentiator; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoDecision.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoDecision.java new file mode 100644 index 0000000..8e67e27 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoDecision.java @@ -0,0 +1,22 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class ProductPricingStepTwoDecision { + private String benchmarkCompetitor; + private BigDecimal targetPrice; + private BigDecimal grossMarginRate; + private String differentiator; + private String pricingReason; + + public String getBenchmarkCompetitor() { return benchmarkCompetitor; } + public void setBenchmarkCompetitor(String benchmarkCompetitor) { this.benchmarkCompetitor = benchmarkCompetitor; } + public BigDecimal getTargetPrice() { return targetPrice; } + public void setTargetPrice(BigDecimal targetPrice) { this.targetPrice = targetPrice; } + public BigDecimal getGrossMarginRate() { return grossMarginRate; } + public void setGrossMarginRate(BigDecimal grossMarginRate) { this.grossMarginRate = grossMarginRate; } + public String getDifferentiator() { return differentiator; } + public void setDifferentiator(String differentiator) { this.differentiator = differentiator; } + public String getPricingReason() { return pricingReason; } + public void setPricingReason(String pricingReason) { this.pricingReason = pricingReason; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationRequest.java new file mode 100644 index 0000000..b4ca384 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationRequest.java @@ -0,0 +1,17 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class ProductPricingStepTwoValidationRequest { + private BigDecimal unitCost; + private List competitorRows; + private ProductPricingStepTwoDecision decision; + + public BigDecimal getUnitCost() { return unitCost; } + public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; } + public List getCompetitorRows() { return competitorRows; } + public void setCompetitorRows(List competitorRows) { this.competitorRows = competitorRows; } + public ProductPricingStepTwoDecision getDecision() { return decision; } + public void setDecision(ProductPricingStepTwoDecision decision) { this.decision = decision; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationResult.java new file mode 100644 index 0000000..25bc98b --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductPricingStepTwoValidationResult.java @@ -0,0 +1,19 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductPricingStepTwoValidationResult { + private boolean valid; + private String message; + private List competitorRows; + private ProductPricingStepTwoDecision decision; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public List getCompetitorRows() { return competitorRows; } + public void setCompetitorRows(List competitorRows) { this.competitorRows = competitorRows; } + public ProductPricingStepTwoDecision getDecision() { return decision; } + public void setDecision(ProductPricingStepTwoDecision decision) { this.decision = decision; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFiveService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFiveService.java new file mode 100644 index 0000000..f01f433 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFiveService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationResult; + +public interface ProductPricingStepFiveService { + ProductPricingStepFiveValidationResult validate(ProductPricingStepFiveValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFourService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFourService.java new file mode 100644 index 0000000..95ccfee --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepFourService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationResult; + +public interface ProductPricingStepFourService { + ProductPricingStepFourValidationResult validate(ProductPricingStepFourValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepOneService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepOneService.java new file mode 100644 index 0000000..1f2955c --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepOneService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationResult; + +public interface ProductPricingStepOneService { + ProductPricingStepOneValidationResult validate(ProductPricingStepOneValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepThreeService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepThreeService.java new file mode 100644 index 0000000..4900a40 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepThreeService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationResult; + +public interface ProductPricingStepThreeService { + ProductPricingStepThreeValidationResult validate(ProductPricingStepThreeValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepTwoService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepTwoService.java new file mode 100644 index 0000000..f0dab17 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductPricingStepTwoService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationResult; + +public interface ProductPricingStepTwoService { + ProductPricingStepTwoValidationResult validate(ProductPricingStepTwoValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java index 9152fda..6df9d6f 100644 --- a/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java @@ -33,7 +33,7 @@ public class MarketOpportunitySelectionStepOneServiceImpl implements MarketOppor @PostConstruct public void initializeStandardAnswers() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS training_step_standard_answer (id varchar(64) NOT NULL COMMENT 'primary id',task_key varchar(128) NOT NULL COMMENT 'training task key',step_no int NOT NULL COMMENT 'step number',answer_json longtext NOT NULL COMMENT 'standard answer json',create_time datetime NULL,update_time datetime NULL,PRIMARY KEY (id),UNIQUE KEY uk_training_step_standard_answer (task_key, step_no)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='training step standard answer'"); - jdbcTemplate.update("INSERT INTO training_step_standard_answer (id, task_key, step_no, answer_json, create_time, update_time) VALUES (?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE answer_json = VALUES(answer_json), update_time = NOW()", "market-opportunity-selection-step-1", TASK_KEY, STEP_NO, JSON.toJSONString(DEFAULT_ANSWERS)); + jdbcTemplate.update("INSERT IGNORE INTO training_step_standard_answer (id, task_key, step_no, answer_json, create_time, update_time) VALUES (?, ?, ?, ?, NOW(), NOW())", "market-opportunity-selection-step-1", TASK_KEY, STEP_NO, JSON.toJSONString(DEFAULT_ANSWERS)); } @Override diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImpl.java new file mode 100644 index 0000000..c48ffa6 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImpl.java @@ -0,0 +1,84 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveMethodResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationResult; +import com.sztzjy.linkCommerce.service.ProductPricingStepFiveService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class ProductPricingStepFiveServiceImpl implements ProductPricingStepFiveService { + private static final List REQUIRED_METHOD_KEYS = Arrays.asList("cost-markup", "competitive", "value-based", "psm"); + private static final BigDecimal MIN_MARGIN_RATE = new BigDecimal("-100"); + private static final BigDecimal MAX_MARGIN_RATE = new BigDecimal("100"); + + @Override + public ProductPricingStepFiveValidationResult validate(ProductPricingStepFiveValidationRequest request, JwtUser user) { + requireStudent(user); + if (request == null || StringUtils.isBlank(request.getSelectedCostMarkupChannel())) { + throw badRequest("请选择用于汇总的成本加成定价渠道"); + } + if (request.getMethodResults() == null || request.getMethodResults().size() != REQUIRED_METHOD_KEYS.size()) { + throw badRequest("请完整汇总四种定价方法的测算结果"); + } + + List results = new ArrayList<>(); + Set keys = new HashSet<>(); + for (ProductPricingStepFiveMethodResult source : request.getMethodResults()) { + if (source == null || StringUtils.isAnyBlank(source.getKey(), source.getMethodName()) + || source.getMeasuredPrice() == null || source.getMeasuredPrice().compareTo(BigDecimal.ZERO) <= 0 + || source.getGrossMarginRate() == null) { + throw badRequest("请完整填写四种定价方法的测算结果和毛利润率"); + } + String key = source.getKey().trim(); + if (!REQUIRED_METHOD_KEYS.contains(key) || !keys.add(key)) { + throw badRequest("定价方法汇总项不正确或重复"); + } + if (source.getGrossMarginRate().compareTo(MIN_MARGIN_RATE) < 0 || source.getGrossMarginRate().compareTo(MAX_MARGIN_RATE) > 0) { + throw badRequest("毛利润率应在 -100% 至 100% 之间"); + } + ProductPricingStepFiveMethodResult result = new ProductPricingStepFiveMethodResult(); + result.setKey(key); + result.setMethodName(source.getMethodName().trim()); + result.setMeasuredPrice(source.getMeasuredPrice().setScale(2, RoundingMode.HALF_UP)); + result.setGrossMarginRate(source.getGrossMarginRate().setScale(2, RoundingMode.HALF_UP)); + results.add(result); + } + if (!keys.containsAll(REQUIRED_METHOD_KEYS) || StringUtils.isBlank(request.getFinalAnalysis())) { + throw badRequest("请填写最终量产定价分析"); + } + + ProductPricingStepFiveValidationResult result = new ProductPricingStepFiveValidationResult(); + result.setValid(true); + result.setMessage("最终量产定价汇总已校验"); + result.setSelectedCostMarkupChannel(request.getSelectedCostMarkupChannel().trim()); + result.setMethodResults(results); + result.setFinalAnalysis(request.getFinalAnalysis().trim()); + return result; + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可提交实训答案"); + } + } + + private ServiceException badRequest(String message) { + return new ServiceException(HttpStatus.BAD_REQUEST, message); + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImpl.java new file mode 100644 index 0000000..3194538 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImpl.java @@ -0,0 +1,93 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourPricePoints; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourPsmRow; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationResult; +import com.sztzjy.linkCommerce.service.ProductPricingStepFourService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class ProductPricingStepFourServiceImpl implements ProductPricingStepFourService { + private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); + private static final List REQUIRED_PRICE_RANGES = Arrays.asList( + "≤100元", "100~150元", "150~200元", "200~250元", "250~300元", "≥300元"); + + @Override + public ProductPricingStepFourValidationResult validate(ProductPricingStepFourValidationRequest request, JwtUser user) { + requireStudent(user); + if (request == null || request.getPsmRows() == null || request.getPsmRows().size() < REQUIRED_PRICE_RANGES.size()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写 6 个固定价格区间的 PSM 累计占比"); + } + + List rows = new ArrayList<>(); + Set ids = new HashSet<>(); + Set priceRanges = new HashSet<>(); + for (int index = 0; index < request.getPsmRows().size(); index++) { + ProductPricingStepFourPsmRow source = request.getPsmRows().get(index); + if (source == null || StringUtils.isAnyBlank(source.getId(), source.getPriceRange())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写第 " + (index + 1) + " 行 PSM 数据"); + } + if (!ids.add(source.getId().trim()) || !priceRanges.add(source.getPriceRange().trim())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "PSM 价格区间不能重复"); + } + ProductPricingStepFourPsmRow row = new ProductPricingStepFourPsmRow(); + row.setId(source.getId().trim()); + row.setPriceRange(source.getPriceRange().trim()); + row.setTooExpensive(normalizePercentage(source.getTooExpensive(), index, "太贵不买")); + row.setTooCheap(normalizePercentage(source.getTooCheap(), index, "太便宜不买")); + row.setExpensiveAcceptable(normalizePercentage(source.getExpensiveAcceptable(), index, "偏高可接受")); + row.setCheapValue(normalizePercentage(source.getCheapValue(), index, "便宜划算")); + rows.add(row); + } + if (!priceRanges.containsAll(REQUIRED_PRICE_RANGES)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请保留并填写 6 个固定价格区间"); + } + + ProductPricingStepFourPricePoints sourcePoints = request.getPricePoints(); + if (sourcePoints == null || sourcePoints.getOptimalPrice() == null || sourcePoints.getOptimalPrice().compareTo(BigDecimal.ZERO) <= 0 + || sourcePoints.getAcceptablePrice() == null || sourcePoints.getAcceptablePrice().compareTo(BigDecimal.ZERO) <= 0 + || StringUtils.isBlank(sourcePoints.getAcceptableRange())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写最优价格点、可接受价格点和可接受价格范围"); + } + ProductPricingStepFourPricePoints pricePoints = new ProductPricingStepFourPricePoints(); + pricePoints.setOptimalPrice(sourcePoints.getOptimalPrice().setScale(2, RoundingMode.HALF_UP)); + pricePoints.setAcceptablePrice(sourcePoints.getAcceptablePrice().setScale(2, RoundingMode.HALF_UP)); + pricePoints.setAcceptableRange(sourcePoints.getAcceptableRange().trim()); + + ProductPricingStepFourValidationResult result = new ProductPricingStepFourValidationResult(); + result.setValid(true); + result.setMessage("PSM 价格敏感度测试已校验"); + result.setPsmRows(rows); + result.setPricePoints(pricePoints); + return result; + } + + private BigDecimal normalizePercentage(BigDecimal value, int rowIndex, String name) { + if (value == null || value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(ONE_HUNDRED) > 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "第 " + (rowIndex + 1) + " 行“" + name + "”累计占比应在 0%–100% 之间"); + } + return value.setScale(2, RoundingMode.HALF_UP); + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImpl.java new file mode 100644 index 0000000..3ddc199 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImpl.java @@ -0,0 +1,88 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneChannel; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationResult; +import com.sztzjy.linkCommerce.service.ProductPricingStepOneService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +public class ProductPricingStepOneServiceImpl implements ProductPricingStepOneService { + private static final Map> CHANNEL_RANGES = new LinkedHashMap<>(); + + static { + CHANNEL_RANGES.put("dealer", Arrays.asList(new BigDecimal("15"), new BigDecimal("25"))); + CHANNEL_RANGES.put("direct-retail", Arrays.asList(new BigDecimal("40"), new BigDecimal("60"))); + CHANNEL_RANGES.put("premium-brand", Arrays.asList(new BigDecimal("70"), new BigDecimal("80"))); + } + + @Override + public ProductPricingStepOneValidationResult validate(ProductPricingStepOneValidationRequest request, JwtUser user) { + requireStudent(user); + if (request == null || request.getUnitCost() == null || request.getUnitCost().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "单台综合成本必须大于 0"); + } + List rows = request.getPricingRows(); + if (rows == null || rows.size() != CHANNEL_RANGES.size()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写三个渠道的加成率"); + } + + Map source = new LinkedHashMap<>(); + for (ProductPricingStepOneChannel row : rows) { + String id = row == null ? null : StringUtils.trimToNull(row.getId()); + if (id == null || source.put(id, row) != null) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "渠道信息不正确,请重新填写"); + } + } + + BigDecimal normalizedCost = request.getUnitCost().setScale(2, RoundingMode.HALF_UP); + List normalized = new ArrayList<>(); + for (Map.Entry> entry : CHANNEL_RANGES.entrySet()) { + ProductPricingStepOneChannel row = source.get(entry.getKey()); + if (row == null || row.getMarkupRate() == null) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写完整的渠道加成率"); + } + BigDecimal markupRate = row.getMarkupRate(); + BigDecimal min = entry.getValue().get(0); + BigDecimal max = entry.getValue().get(1); + if (markupRate.compareTo(min) < 0 || markupRate.compareTo(max) > 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, + "渠道加成率需在 " + min.toPlainString() + "%~" + max.toPlainString() + "% 范围内"); + } + ProductPricingStepOneChannel item = new ProductPricingStepOneChannel(); + item.setId(entry.getKey()); + item.setMarkupRate(markupRate.setScale(2, RoundingMode.HALF_UP)); + item.setSalePrice(normalizedCost.multiply(BigDecimal.ONE.add(markupRate.movePointLeft(2))) + .setScale(2, RoundingMode.HALF_UP)); + normalized.add(item); + } + + ProductPricingStepOneValidationResult result = new ProductPricingStepOneValidationResult(); + result.setValid(true); + result.setMessage("渠道加成定价已校验"); + result.setUnitCost(normalizedCost); + result.setPricingRows(normalized); + return result; + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImpl.java new file mode 100644 index 0000000..5357834 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImpl.java @@ -0,0 +1,80 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValueItem; +import com.sztzjy.linkCommerce.service.ProductPricingStepThreeService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class ProductPricingStepThreeServiceImpl implements ProductPricingStepThreeService { + private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); + + @Override + public ProductPricingStepThreeValidationResult validate(ProductPricingStepThreeValidationRequest request, JwtUser user) { + requireStudent(user); + if (request == null || request.getValueItems() == null || request.getValueItems().isEmpty()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请至少填写一项可替代产品或功能的价值感知"); + } + if (request.getDiscountRate() == null || request.getDiscountRate().compareTo(BigDecimal.ZERO) <= 0 + || request.getDiscountRate().compareTo(ONE_HUNDRED) > 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "利润让渡折扣率应大于 0 且不超过 100%"); + } + + List items = new ArrayList<>(); + Set ids = new HashSet<>(); + BigDecimal total = BigDecimal.ZERO; + for (int index = 0; index < request.getValueItems().size(); index++) { + ProductPricingStepThreeValueItem source = request.getValueItems().get(index); + if (source == null || StringUtils.isAnyBlank(source.getId(), source.getName())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写第 " + (index + 1) + " 项价值感知"); + } + if (!ids.add(source.getId().trim())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "价值感知条目不能重复"); + } + if (source.getPerceivedValue() == null || source.getPerceivedValue().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "第 " + (index + 1) + " 项价值感知金额必须大于 0"); + } + ProductPricingStepThreeValueItem item = new ProductPricingStepThreeValueItem(); + item.setId(source.getId().trim()); + item.setName(source.getName().trim()); + item.setPerceivedValue(source.getPerceivedValue().setScale(2, RoundingMode.HALF_UP)); + items.add(item); + total = total.add(item.getPerceivedValue()); + } + + BigDecimal perceivedValueTotal = total.setScale(2, RoundingMode.HALF_UP); + BigDecimal discountRate = request.getDiscountRate().setScale(2, RoundingMode.HALF_UP); + BigDecimal valueBasedPrice = perceivedValueTotal.multiply(discountRate).movePointLeft(2) + .setScale(2, RoundingMode.HALF_UP); + + ProductPricingStepThreeValidationResult result = new ProductPricingStepThreeValidationResult(); + result.setValid(true); + result.setMessage("用户感知价值定价已校验"); + result.setValueItems(items); + result.setPerceivedValueTotal(perceivedValueTotal); + result.setDiscountRate(discountRate); + result.setValueBasedPrice(valueBasedPrice); + return result; + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImpl.java new file mode 100644 index 0000000..3616eea --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImpl.java @@ -0,0 +1,97 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoCompetitor; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoDecision; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationResult; +import com.sztzjy.linkCommerce.service.ProductPricingStepTwoService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class ProductPricingStepTwoServiceImpl implements ProductPricingStepTwoService { + private static final int REQUIRED_COMPETITOR_COUNT = 4; + + @Override + public ProductPricingStepTwoValidationResult validate(ProductPricingStepTwoValidationRequest request, JwtUser user) { + requireStudent(user); + if (request == null || request.getUnitCost() == null || request.getUnitCost().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成第 1 步并填写单台综合成本"); + } + List sourceRows = request.getCompetitorRows(); + if (sourceRows == null || sourceRows.size() != REQUIRED_COMPETITOR_COUNT) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写 4 个竞品信息"); + } + + List competitors = new ArrayList<>(); + Set productTypes = new HashSet<>(); + for (int index = 0; index < sourceRows.size(); index++) { + ProductPricingStepTwoCompetitor row = sourceRows.get(index); + if (row == null || StringUtils.isAnyBlank(row.getId(), row.getProductType(), row.getBrand(), row.getDifferentiator())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写竞品 " + (index + 1) + " 的信息"); + } + if (row.getEcommercePrice() == null || row.getEcommercePrice().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "竞品 " + (index + 1) + " 的电商售价必须大于 0"); + } + String productType = row.getProductType().trim(); + if (!productTypes.add(productType)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "竞品产品类型不能重复,请使用可区分的竞品名称"); + } + ProductPricingStepTwoCompetitor item = new ProductPricingStepTwoCompetitor(); + item.setId(row.getId().trim()); + item.setProductType(productType); + item.setBrand(row.getBrand().trim()); + item.setEcommercePrice(row.getEcommercePrice().setScale(2, RoundingMode.HALF_UP)); + item.setDifferentiator(row.getDifferentiator().trim()); + competitors.add(item); + } + + ProductPricingStepTwoDecision sourceDecision = request.getDecision(); + if (sourceDecision == null || StringUtils.isAnyBlank(sourceDecision.getBenchmarkCompetitor(), + sourceDecision.getDifferentiator(), sourceDecision.getPricingReason())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写对标竞品、差异化优势和定价原因"); + } + if (!productTypes.contains(sourceDecision.getBenchmarkCompetitor().trim())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "对标竞品必须从上方已填写的竞品中选择"); + } + if (sourceDecision.getTargetPrice() == null || sourceDecision.getTargetPrice().compareTo(BigDecimal.ZERO) <= 0) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "本产品定价必须大于 0"); + } + + BigDecimal targetPrice = sourceDecision.getTargetPrice().setScale(2, RoundingMode.HALF_UP); + BigDecimal grossMarginRate = targetPrice.subtract(request.getUnitCost()).divide(targetPrice, 6, RoundingMode.HALF_UP) + .movePointRight(2).setScale(2, RoundingMode.HALF_UP); + ProductPricingStepTwoDecision decision = new ProductPricingStepTwoDecision(); + decision.setBenchmarkCompetitor(sourceDecision.getBenchmarkCompetitor().trim()); + decision.setTargetPrice(targetPrice); + decision.setGrossMarginRate(grossMarginRate); + decision.setDifferentiator(sourceDecision.getDifferentiator().trim()); + decision.setPricingReason(sourceDecision.getPricingReason().trim()); + + ProductPricingStepTwoValidationResult result = new ProductPricingStepTwoValidationResult(); + result.setValid(true); + result.setMessage("竞品价格带与定价已校验"); + result.setCompetitorRows(competitors); + result.setDecision(decision); + return result; + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepFourServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepFourServiceImplTest.java index cbc4172..fbe8d99 100644 --- a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepFourServiceImplTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepFourServiceImplTest.java @@ -36,7 +36,7 @@ class MarketOpportunitySelectionStepFourServiceImplTest { private MarketOpportunitySelectionStepFourServiceImpl serviceWithStepThree() { MarketOpportunitySelectionStepFourServiceImpl service = new MarketOpportunitySelectionStepFourServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); - StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep3Answer("{\"scores\":{}}"); + StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep3Answer("{\"scores\":{\"smart-fitness-mirror\":86.50,\"smart-bathroom-mirror\":82.00,\"smart-beauty-mirror\":78.25}}"); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); return service; diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java index 52c97c8..b84b930 100644 --- a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java @@ -29,7 +29,7 @@ class MarketOpportunitySelectionStepOneServiceImplTest { void checkReturnsPerIndicatorFeedbackWithoutExposingTheAnswer() { MarketOpportunitySelectionStepOneServiceImpl service = new MarketOpportunitySelectionStepOneServiceImpl(standardAnswerJdbcTemplate()); MarketOpportunitySelectionStepOneCheckResult result = service.check(requestWithChangedSecondCategory("市场规模"), student()); - assertEquals(7, result.getCorrectCount()); assertEquals(8, result.getTotalCount()); assertFalse(result.isAllCorrect()); assertTrue(result.getItems().get(0).isCorrect()); assertFalse(result.getItems().get(1).isCorrect()); assertFalse(JSON.toJSONString(result).contains("correctCategory")); + assertEquals(8, result.getCorrectCount()); assertEquals(9, result.getTotalCount()); assertFalse(result.isAllCorrect()); assertTrue(result.getItems().get(0).isCorrect()); assertFalse(result.getItems().get(1).isCorrect()); assertFalse(JSON.toJSONString(result).contains("correctCategory")); } @Test @@ -48,7 +48,7 @@ class MarketOpportunitySelectionStepOneServiceImplTest { } private JdbcTemplate standardAnswerJdbcTemplate() { JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); Map answers = new LinkedHashMap<>(); for (MarketOpportunitySelectionStepOneCheckRequest.Item item : defaultAnswers()) answers.put(item.getId(), item.getCategory()); when(jdbcTemplate.queryForObject(any(String.class), any(Class.class), any(), any())).thenReturn(JSON.toJSONString(answers)); return jdbcTemplate; } - private List defaultAnswers() { return Arrays.asList(item("search-count", "市场规模"), item("demand-supply-ratio", "竞争强度"), item("transaction-amount", "市场规模"), item("transaction-growth", "市场潜力"), item("category-transaction-growth", "市场潜力"), item("online-products", "竞争强度"), item("online-merchants", "竞争强度"), item("organic-traffic-ratio", "运营难度")); } + private List defaultAnswers() { return Arrays.asList(item("search-count", "市场规模"), item("demand-supply-ratio", "竞争强度"), item("transaction-amount", "市场规模"), item("transaction-growth", "市场潜力"), item("category-transaction-growth", "市场潜力"), item("online-products", "竞争强度"), item("online-merchants", "竞争强度"), item("organic-traffic-ratio", "运营难度"), item("return-rate", "运营难度")); } private MarketOpportunitySelectionStepOneCheckRequest requestWithChangedSecondCategory(String category) { List items = defaultAnswers(); items.get(1).setCategory(category); return request(items); } private MarketOpportunitySelectionStepOneCheckRequest request(List items) { MarketOpportunitySelectionStepOneCheckRequest request = new MarketOpportunitySelectionStepOneCheckRequest(); request.setItems(items); return request; } private MarketOpportunitySelectionStepOneCheckRequest.Item item(String id, String category) { MarketOpportunitySelectionStepOneCheckRequest.Item item = new MarketOpportunitySelectionStepOneCheckRequest.Item(); item.setId(id); item.setCategory(category); return item; } diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepThreeServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepThreeServiceImplTest.java index 9eb4f43..517849d 100644 --- a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepThreeServiceImplTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepThreeServiceImplTest.java @@ -33,7 +33,7 @@ class MarketOpportunitySelectionStepThreeServiceImplTest { MarketOpportunitySelectionStepThreeServiceImpl service = new MarketOpportunitySelectionStepThreeServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(null); assertThrows(ServiceException.class, () -> service.validate(completedRequest(), student())); } - private MarketOpportunitySelectionStepThreeServiceImpl serviceWithStepTwo() { MarketOpportunitySelectionStepThreeServiceImpl service = new MarketOpportunitySelectionStepThreeServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep2Answer("{\"weights\":{}}"); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); return service; } + private MarketOpportunitySelectionStepThreeServiceImpl serviceWithStepTwo() { MarketOpportunitySelectionStepThreeServiceImpl service = new MarketOpportunitySelectionStepThreeServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep2Answer("{\"primaryWeights\":{\"市场规模\":25,\"市场潜力\":25,\"竞争强度\":25,\"运营难度\":25},\"dimensionWeights\":{\"search-count\":50,\"transaction-amount\":50,\"transaction-growth\":50,\"category-transaction-growth\":50,\"demand-supply-ratio\":34,\"online-products\":33,\"online-merchants\":33,\"organic-traffic-ratio\":50,\"return-rate\":50}}"); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); return service; } private MarketOpportunitySelectionStepThreeValidationRequest completedRequest() { MarketOpportunitySelectionStepThreeValidationRequest request = new MarketOpportunitySelectionStepThreeValidationRequest(); ArrayList scores = new ArrayList<>(); Arrays.asList("smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror").forEach(id -> { MarketOpportunitySelectionStepThreeValidationRequest.Score score = new MarketOpportunitySelectionStepThreeValidationRequest.Score(); score.setId(id); score.setScore(new BigDecimal("86.50")); scores.add(score); }); request.setScores(scores); return request; } private JwtUser student() { JwtUser user = new JwtUser(); user.setUserId("stu-1"); user.setRoleId(4); return user; } } diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImplTest.java new file mode 100644 index 0000000..6ea9f2f --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFiveServiceImplTest.java @@ -0,0 +1,55 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveMethodResult; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFiveValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProductPricingStepFiveServiceImplTest { + @Test + void acceptsAllFourPricingMethodsAndTrimsFinalAnalysis() { + ProductPricingStepFiveValidationResult result = new ProductPricingStepFiveServiceImpl() + .validate(validRequest(), student()); + + assertTrue(result.isValid()); + assertEquals(4, result.getMethodResults().size()); + assertEquals(new BigDecimal("188.00"), result.getMethodResults().get(0).getMeasuredPrice()); + assertEquals("综合四种定价结果,选择兼顾市场接受度和合理利润的最终量产定价。", result.getFinalAnalysis()); + } + + private ProductPricingStepFiveValidationRequest validRequest() { + ProductPricingStepFiveValidationRequest request = new ProductPricingStepFiveValidationRequest(); + request.setSelectedCostMarkupChannel("厂家直接电商零售"); + request.setMethodResults(Arrays.asList( + method("cost-markup", "成本加成定价", "188", "46.81"), + method("competitive", "竞争定价", "199", "49.75"), + method("value-based", "价值定价", "210", "52.38"), + method("psm", "PSM价格敏感度", "180", "44.44") + )); + request.setFinalAnalysis(" 综合四种定价结果,选择兼顾市场接受度和合理利润的最终量产定价。 "); + return request; + } + + private ProductPricingStepFiveMethodResult method(String key, String methodName, String price, String grossMarginRate) { + ProductPricingStepFiveMethodResult method = new ProductPricingStepFiveMethodResult(); + method.setKey(key); + method.setMethodName(methodName); + method.setMeasuredPrice(new BigDecimal(price)); + method.setGrossMarginRate(new BigDecimal(grossMarginRate)); + return method; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("student-1"); + user.setRoleId(4); + return user; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImplTest.java new file mode 100644 index 0000000..cbd19d7 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepFourServiceImplTest.java @@ -0,0 +1,71 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourPricePoints; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourPsmRow; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepFourValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProductPricingStepFourServiceImplTest { + @Test + void validatesSixPriceRangesAndNormalizesPsmPercentages() { + ProductPricingStepFourValidationResult result = new ProductPricingStepFourServiceImpl() + .validate(validRequest(), student()); + + assertTrue(result.isValid()); + assertEquals(6, result.getPsmRows().size()); + assertEquals(new BigDecimal("20.00"), result.getPsmRows().get(0).getTooExpensive()); + assertEquals(new BigDecimal("180.00"), result.getPricePoints().getOptimalPrice()); + } + + @Test + void rejectsPsmPercentageOutsideZeroToOneHundred() { + ProductPricingStepFourValidationRequest request = validRequest(); + request.getPsmRows().get(0).setTooCheap(new BigDecimal("101")); + + assertThrows(ServiceException.class, + () -> new ProductPricingStepFourServiceImpl().validate(request, student())); + } + + private ProductPricingStepFourValidationRequest validRequest() { + ProductPricingStepFourValidationRequest request = new ProductPricingStepFourValidationRequest(); + request.setPsmRows(Arrays.asList( + row("psm-under-100", "≤100元"), row("psm-100-150", "100~150元"), + row("psm-150-200", "150~200元"), row("psm-200-250", "200~250元"), + row("psm-250-300", "250~300元"), row("psm-over-300", "≥300元") + )); + ProductPricingStepFourPricePoints pricePoints = new ProductPricingStepFourPricePoints(); + pricePoints.setOptimalPrice(new BigDecimal("180")); + pricePoints.setAcceptablePrice(new BigDecimal("220")); + pricePoints.setAcceptableRange("150~260元"); + request.setPricePoints(pricePoints); + return request; + } + + private ProductPricingStepFourPsmRow row(String id, String priceRange) { + ProductPricingStepFourPsmRow row = new ProductPricingStepFourPsmRow(); + row.setId(id); + row.setPriceRange(priceRange); + row.setTooExpensive(new BigDecimal("20")); + row.setTooCheap(new BigDecimal("30")); + row.setExpensiveAcceptable(new BigDecimal("40")); + row.setCheapValue(new BigDecimal("50")); + return row; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("student-1"); + user.setRoleId(4); + return user; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImplTest.java new file mode 100644 index 0000000..9d53103 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepOneServiceImplTest.java @@ -0,0 +1,70 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneChannel; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepOneValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProductPricingStepOneServiceImplTest { + @Test + void validatesConfiguredChannelsAndRecalculatesSalePrices() { + ProductPricingStepOneValidationResult result = new ProductPricingStepOneServiceImpl() + .validate(validRequest(), student()); + + assertTrue(result.isValid()); + assertEquals(new BigDecimal("100.00"), result.getUnitCost()); + assertEquals(new BigDecimal("120.00"), result.getPricingRows().get(0).getSalePrice()); + assertEquals(new BigDecimal("150.00"), result.getPricingRows().get(1).getSalePrice()); + assertEquals(new BigDecimal("175.00"), result.getPricingRows().get(2).getSalePrice()); + } + + @Test + void rejectsOutOfRangeMarkupAndNonStudentUser() { + ProductPricingStepOneServiceImpl service = new ProductPricingStepOneServiceImpl(); + ProductPricingStepOneValidationRequest invalid = validRequest(); + invalid.getPricingRows().get(0).setMarkupRate(new BigDecimal("26")); + + assertThrows(ServiceException.class, () -> service.validate(invalid, student())); + assertThrows(ServiceException.class, () -> service.validate(validRequest(), teacher())); + } + + private ProductPricingStepOneValidationRequest validRequest() { + ProductPricingStepOneValidationRequest request = new ProductPricingStepOneValidationRequest(); + request.setUnitCost(new BigDecimal("100")); + request.setPricingRows(Arrays.asList( + row("dealer", "20"), + row("direct-retail", "50"), + row("premium-brand", "75") + )); + return request; + } + + private ProductPricingStepOneChannel row(String id, String markupRate) { + ProductPricingStepOneChannel channel = new ProductPricingStepOneChannel(); + channel.setId(id); + channel.setMarkupRate(new BigDecimal(markupRate)); + return channel; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("student-1"); + user.setRoleId(4); + return user; + } + + private JwtUser teacher() { + JwtUser user = student(); + user.setRoleId(3); + return user; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImplTest.java new file mode 100644 index 0000000..b39a8cc --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepThreeServiceImplTest.java @@ -0,0 +1,59 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValueItem; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepThreeValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProductPricingStepThreeServiceImplTest { + @Test + void totalsPerceivedValueAndCalculatesPriceFromDiscountRate() { + ProductPricingStepThreeValidationResult result = new ProductPricingStepThreeServiceImpl() + .validate(validRequest(), student()); + + assertTrue(result.isValid()); + assertEquals(new BigDecimal("130.00"), result.getPerceivedValueTotal()); + assertEquals(new BigDecimal("80.00"), result.getDiscountRate()); + assertEquals(new BigDecimal("104.00"), result.getValueBasedPrice()); + } + + @Test + void rejectsDiscountRateOutsidePercentageRange() { + ProductPricingStepThreeValidationRequest request = validRequest(); + request.setDiscountRate(new BigDecimal("101")); + + assertThrows(ServiceException.class, + () -> new ProductPricingStepThreeServiceImpl().validate(request, student())); + } + + private ProductPricingStepThreeValidationRequest validRequest() { + ProductPricingStepThreeValidationRequest request = new ProductPricingStepThreeValidationRequest(); + request.setValueItems(Arrays.asList(item("value-1", "闹钟", "20"), item("value-2", "长续航", "110"))); + request.setDiscountRate(new BigDecimal("80")); + return request; + } + + private ProductPricingStepThreeValueItem item(String id, String name, String perceivedValue) { + ProductPricingStepThreeValueItem item = new ProductPricingStepThreeValueItem(); + item.setId(id); + item.setName(name); + item.setPerceivedValue(new BigDecimal(perceivedValue)); + return item; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("student-1"); + user.setRoleId(4); + return user; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImplTest.java new file mode 100644 index 0000000..93abcfe --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductPricingStepTwoServiceImplTest.java @@ -0,0 +1,69 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoCompetitor; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoDecision; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductPricingStepTwoValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProductPricingStepTwoServiceImplTest { + @Test + void validatesFourCompetitorsAndCalculatesGrossMargin() { + ProductPricingStepTwoValidationResult result = new ProductPricingStepTwoServiceImpl() + .validate(validRequest(), student()); + + assertTrue(result.isValid()); + assertEquals(new BigDecimal("33.33"), result.getDecision().getGrossMarginRate()); + assertEquals("竞品2", result.getDecision().getBenchmarkCompetitor()); + } + + @Test + void rejectsBenchmarkThatIsNotAmongEnteredCompetitors() { + ProductPricingStepTwoValidationRequest request = validRequest(); + request.getDecision().setBenchmarkCompetitor("未填写竞品"); + + assertThrows(ServiceException.class, () -> new ProductPricingStepTwoServiceImpl().validate(request, student())); + } + + private ProductPricingStepTwoValidationRequest validRequest() { + ProductPricingStepTwoValidationRequest request = new ProductPricingStepTwoValidationRequest(); + request.setUnitCost(new BigDecimal("100")); + request.setCompetitorRows(Arrays.asList( + competitor("competitor-1", "竞品1"), competitor("competitor-2", "竞品2"), + competitor("competitor-3", "竞品3"), competitor("competitor-4", "竞品4") + )); + ProductPricingStepTwoDecision decision = new ProductPricingStepTwoDecision(); + decision.setBenchmarkCompetitor("竞品2"); + decision.setTargetPrice(new BigDecimal("150")); + decision.setDifferentiator("更长续航"); + decision.setPricingReason("处于可切入价格带"); + request.setDecision(decision); + return request; + } + + private ProductPricingStepTwoCompetitor competitor(String id, String productType) { + ProductPricingStepTwoCompetitor row = new ProductPricingStepTwoCompetitor(); + row.setId(id); + row.setProductType(productType); + row.setBrand("品牌" + id.substring(id.length() - 1)); + row.setEcommercePrice(new BigDecimal("199")); + row.setDifferentiator("差异化优势"); + return row; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("student-1"); + user.setRoleId(4); + return user; + } +}