feat: complete product pricing training steps
parent
298d26da81
commit
b4dd453afb
@ -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; }
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.sztzjy.linkCommerce.entity.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ProductPricingStepFiveValidationRequest {
|
||||
private String selectedCostMarkupChannel;
|
||||
private List<ProductPricingStepFiveMethodResult> methodResults;
|
||||
private String finalAnalysis;
|
||||
|
||||
public String getSelectedCostMarkupChannel() { return selectedCostMarkupChannel; }
|
||||
public void setSelectedCostMarkupChannel(String selectedCostMarkupChannel) { this.selectedCostMarkupChannel = selectedCostMarkupChannel; }
|
||||
public List<ProductPricingStepFiveMethodResult> getMethodResults() { return methodResults; }
|
||||
public void setMethodResults(List<ProductPricingStepFiveMethodResult> methodResults) { this.methodResults = methodResults; }
|
||||
public String getFinalAnalysis() { return finalAnalysis; }
|
||||
public void setFinalAnalysis(String finalAnalysis) { this.finalAnalysis = finalAnalysis; }
|
||||
}
|
||||
@ -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<ProductPricingStepFiveMethodResult> 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<ProductPricingStepFiveMethodResult> getMethodResults() { return methodResults; }
|
||||
public void setMethodResults(List<ProductPricingStepFiveMethodResult> methodResults) { this.methodResults = methodResults; }
|
||||
public String getFinalAnalysis() { return finalAnalysis; }
|
||||
public void setFinalAnalysis(String finalAnalysis) { this.finalAnalysis = finalAnalysis; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.sztzjy.linkCommerce.entity.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ProductPricingStepFourValidationRequest {
|
||||
private List<ProductPricingStepFourPsmRow> psmRows;
|
||||
private ProductPricingStepFourPricePoints pricePoints;
|
||||
|
||||
public List<ProductPricingStepFourPsmRow> getPsmRows() { return psmRows; }
|
||||
public void setPsmRows(List<ProductPricingStepFourPsmRow> psmRows) { this.psmRows = psmRows; }
|
||||
public ProductPricingStepFourPricePoints getPricePoints() { return pricePoints; }
|
||||
public void setPricePoints(ProductPricingStepFourPricePoints pricePoints) { this.pricePoints = pricePoints; }
|
||||
}
|
||||
@ -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<ProductPricingStepFourPsmRow> 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<ProductPricingStepFourPsmRow> getPsmRows() { return psmRows; }
|
||||
public void setPsmRows(List<ProductPricingStepFourPsmRow> psmRows) { this.psmRows = psmRows; }
|
||||
public ProductPricingStepFourPricePoints getPricePoints() { return pricePoints; }
|
||||
public void setPricePoints(ProductPricingStepFourPricePoints pricePoints) { this.pricePoints = pricePoints; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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<ProductPricingStepOneChannel> pricingRows;
|
||||
|
||||
public BigDecimal getUnitCost() { return unitCost; }
|
||||
public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; }
|
||||
public List<ProductPricingStepOneChannel> getPricingRows() { return pricingRows; }
|
||||
public void setPricingRows(List<ProductPricingStepOneChannel> pricingRows) { this.pricingRows = pricingRows; }
|
||||
}
|
||||
@ -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<ProductPricingStepOneChannel> 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<ProductPricingStepOneChannel> getPricingRows() { return pricingRows; }
|
||||
public void setPricingRows(List<ProductPricingStepOneChannel> pricingRows) { this.pricingRows = pricingRows; }
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.sztzjy.linkCommerce.entity.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductPricingStepThreeValidationRequest {
|
||||
private List<ProductPricingStepThreeValueItem> valueItems;
|
||||
private BigDecimal discountRate;
|
||||
|
||||
public List<ProductPricingStepThreeValueItem> getValueItems() { return valueItems; }
|
||||
public void setValueItems(List<ProductPricingStepThreeValueItem> valueItems) { this.valueItems = valueItems; }
|
||||
public BigDecimal getDiscountRate() { return discountRate; }
|
||||
public void setDiscountRate(BigDecimal discountRate) { this.discountRate = discountRate; }
|
||||
}
|
||||
@ -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<ProductPricingStepThreeValueItem> 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<ProductPricingStepThreeValueItem> getValueItems() { return valueItems; }
|
||||
public void setValueItems(List<ProductPricingStepThreeValueItem> 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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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<ProductPricingStepTwoCompetitor> competitorRows;
|
||||
private ProductPricingStepTwoDecision decision;
|
||||
|
||||
public BigDecimal getUnitCost() { return unitCost; }
|
||||
public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; }
|
||||
public List<ProductPricingStepTwoCompetitor> getCompetitorRows() { return competitorRows; }
|
||||
public void setCompetitorRows(List<ProductPricingStepTwoCompetitor> competitorRows) { this.competitorRows = competitorRows; }
|
||||
public ProductPricingStepTwoDecision getDecision() { return decision; }
|
||||
public void setDecision(ProductPricingStepTwoDecision decision) { this.decision = decision; }
|
||||
}
|
||||
@ -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<ProductPricingStepTwoCompetitor> 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<ProductPricingStepTwoCompetitor> getCompetitorRows() { return competitorRows; }
|
||||
public void setCompetitorRows(List<ProductPricingStepTwoCompetitor> competitorRows) { this.competitorRows = competitorRows; }
|
||||
public ProductPricingStepTwoDecision getDecision() { return decision; }
|
||||
public void setDecision(ProductPricingStepTwoDecision decision) { this.decision = decision; }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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<String> 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<ProductPricingStepFiveMethodResult> results = new ArrayList<>();
|
||||
Set<String> 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);
|
||||
}
|
||||
}
|
||||
@ -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<String, List<BigDecimal>> 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<ProductPricingStepOneChannel> rows = request.getPricingRows();
|
||||
if (rows == null || rows.size() != CHANNEL_RANGES.size()) {
|
||||
throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写三个渠道的加成率");
|
||||
}
|
||||
|
||||
Map<String, ProductPricingStepOneChannel> 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<ProductPricingStepOneChannel> normalized = new ArrayList<>();
|
||||
for (Map.Entry<String, List<BigDecimal>> 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, "仅学生可进行实训校验");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<ProductPricingStepThreeValueItem> items = new ArrayList<>();
|
||||
Set<String> 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, "仅学生可进行实训校验");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<ProductPricingStepTwoCompetitor> sourceRows = request.getCompetitorRows();
|
||||
if (sourceRows == null || sourceRows.size() != REQUIRED_COMPETITOR_COUNT) {
|
||||
throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写 4 个竞品信息");
|
||||
}
|
||||
|
||||
List<ProductPricingStepTwoCompetitor> competitors = new ArrayList<>();
|
||||
Set<String> 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, "仅学生可进行实训校验");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue