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 3ac9fad..03e71f1 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -357,6 +357,15 @@ public class StudentTrainingAnswerController { @Autowired private ProductDiagnosisIterationStepThreeService productDiagnosisIterationStepThreeService; + @Autowired + private com.sztzjy.linkCommerce.service.BusinessFeasibilityStepOneService businessFeasibilityStepOneService; + + @Autowired + private com.sztzjy.linkCommerce.service.BusinessFeasibilityStepTwoService businessFeasibilityStepTwoService; + + @Autowired + private com.sztzjy.linkCommerce.service.BusinessFeasibilityStepThreeService businessFeasibilityStepThreeService; + @Autowired private SupplierEvaluationStepTwoService supplierEvaluationStepTwoService; @Autowired @@ -1056,6 +1065,39 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/business-feasibility/step-1/validate") + @ApiOperation("商业可行性论证第一步校验") + public ResultEntity validateBusinessFeasibilityStepOne( + @RequestBody com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepOneValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", businessFeasibilityStepOneService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/business-feasibility/step-2/validate") + @ApiOperation("商业可行性论证第二步校验") + public ResultEntity validateBusinessFeasibilityStepTwo( + @RequestBody com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", businessFeasibilityStepTwoService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + + @PostMapping("/business-feasibility/step-3/validate") + @ApiOperation("商业可行性论证第三步校验") + public ResultEntity validateBusinessFeasibilityStepThree( + @RequestBody com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", businessFeasibilityStepThreeService.validate(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + @DeleteMapping("/{taskKey}") @ApiOperation("删除当前学生指定实训任务答案") public ResultEntity delete(@PathVariable String taskKey, diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationRequest.java new file mode 100644 index 0000000..8ca37b9 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationRequest.java @@ -0,0 +1,116 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class BusinessFeasibilityStepOneValidationRequest { + private ProjectOverview projectOverview; + private MarketAnalysis marketAnalysis; + private List targetUsers; + private List businessModels; + private List costs; + private BigDecimal targetPrice; + private List risks; + + public ProjectOverview getProjectOverview() { return projectOverview; } + public void setProjectOverview(ProjectOverview projectOverview) { this.projectOverview = projectOverview; } + public MarketAnalysis getMarketAnalysis() { return marketAnalysis; } + public void setMarketAnalysis(MarketAnalysis marketAnalysis) { this.marketAnalysis = marketAnalysis; } + public List getTargetUsers() { return targetUsers; } + public void setTargetUsers(List targetUsers) { this.targetUsers = targetUsers; } + public List getBusinessModels() { return businessModels; } + public void setBusinessModels(List businessModels) { this.businessModels = businessModels; } + public List getCosts() { return costs; } + public void setCosts(List costs) { this.costs = costs; } + public BigDecimal getTargetPrice() { return targetPrice; } + public void setTargetPrice(BigDecimal targetPrice) { this.targetPrice = targetPrice; } + public List getRisks() { return risks; } + public void setRisks(List risks) { this.risks = risks; } + + public static class ProjectOverview { + private String projectBackground; + private String productPositioning; + private String projectGoals; + private String macroEnvironmentConclusion; + private String competitiveLandscapeConclusion; + public String getProjectBackground() { return projectBackground; } + public void setProjectBackground(String projectBackground) { this.projectBackground = projectBackground; } + public String getProductPositioning() { return productPositioning; } + public void setProductPositioning(String productPositioning) { this.productPositioning = productPositioning; } + public String getProjectGoals() { return projectGoals; } + public void setProjectGoals(String projectGoals) { this.projectGoals = projectGoals; } + public String getMacroEnvironmentConclusion() { return macroEnvironmentConclusion; } + public void setMacroEnvironmentConclusion(String macroEnvironmentConclusion) { this.macroEnvironmentConclusion = macroEnvironmentConclusion; } + public String getCompetitiveLandscapeConclusion() { return competitiveLandscapeConclusion; } + public void setCompetitiveLandscapeConclusion(String competitiveLandscapeConclusion) { this.competitiveLandscapeConclusion = competitiveLandscapeConclusion; } + } + + public static class MarketAnalysis { + private BigDecimal annualMarketSize; + private BigDecimal monthlyGrowthRate; + private BigDecimal topBrandMarketShare; + private String competitorsPriceRange; + private BigDecimal marketConcentrationCr3; + public BigDecimal getAnnualMarketSize() { return annualMarketSize; } + public void setAnnualMarketSize(BigDecimal annualMarketSize) { this.annualMarketSize = annualMarketSize; } + public BigDecimal getMonthlyGrowthRate() { return monthlyGrowthRate; } + public void setMonthlyGrowthRate(BigDecimal monthlyGrowthRate) { this.monthlyGrowthRate = monthlyGrowthRate; } + public BigDecimal getTopBrandMarketShare() { return topBrandMarketShare; } + public void setTopBrandMarketShare(BigDecimal topBrandMarketShare) { this.topBrandMarketShare = topBrandMarketShare; } + public String getCompetitorsPriceRange() { return competitorsPriceRange; } + public void setCompetitorsPriceRange(String competitorsPriceRange) { this.competitorsPriceRange = competitorsPriceRange; } + public BigDecimal getMarketConcentrationCr3() { return marketConcentrationCr3; } + public void setMarketConcentrationCr3(BigDecimal marketConcentrationCr3) { this.marketConcentrationCr3 = marketConcentrationCr3; } + } + + public static class TargetUser { + private String userGroup; + private String scenario; + private String coreNeed; + private String spendingPower; + public String getUserGroup() { return userGroup; } + public void setUserGroup(String userGroup) { this.userGroup = userGroup; } + public String getScenario() { return scenario; } + public void setScenario(String scenario) { this.scenario = scenario; } + public String getCoreNeed() { return coreNeed; } + public void setCoreNeed(String coreNeed) { this.coreNeed = coreNeed; } + public String getSpendingPower() { return spendingPower; } + public void setSpendingPower(String spendingPower) { this.spendingPower = spendingPower; } + } + + public static class BusinessModel { + private String module; + private String strategy; + private String explanation; + public String getModule() { return module; } + public void setModule(String module) { this.module = module; } + public String getStrategy() { return strategy; } + public void setStrategy(String strategy) { this.strategy = strategy; } + public String getExplanation() { return explanation; } + public void setExplanation(String explanation) { this.explanation = explanation; } + } + + public static class CostItem { + private String id; + private BigDecimal amount; + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public BigDecimal getAmount() { return amount; } + public void setAmount(BigDecimal amount) { this.amount = amount; } + } + + public static class Risk { + private String type; + private String description; + private String impact; + private String response; + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public String getImpact() { return impact; } + public void setImpact(String impact) { this.impact = impact; } + public String getResponse() { return response; } + public void setResponse(String response) { this.response = response; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationResult.java new file mode 100644 index 0000000..0e92dfb --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepOneValidationResult.java @@ -0,0 +1,21 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.math.BigDecimal; + +public class BusinessFeasibilityStepOneValidationResult { + private boolean valid; + private String message; + private BigDecimal unitTotalCost; + private BigDecimal unitGrossProfit; + private BigDecimal grossMargin; + 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 getUnitTotalCost() { return unitTotalCost; } + public void setUnitTotalCost(BigDecimal unitTotalCost) { this.unitTotalCost = unitTotalCost; } + public BigDecimal getUnitGrossProfit() { return unitGrossProfit; } + public void setUnitGrossProfit(BigDecimal unitGrossProfit) { this.unitGrossProfit = unitGrossProfit; } + public BigDecimal getGrossMargin() { return grossMargin; } + public void setGrossMargin(BigDecimal grossMargin) { this.grossMargin = grossMargin; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationRequest.java new file mode 100644 index 0000000..fbf018c --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationRequest.java @@ -0,0 +1,10 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class BusinessFeasibilityStepThreeValidationRequest { + private String reportUrl; + private String reportName; + public String getReportUrl() { return reportUrl; } + public void setReportUrl(String reportUrl) { this.reportUrl = reportUrl; } + public String getReportName() { return reportName; } + public void setReportName(String reportName) { this.reportName = reportName; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationResult.java new file mode 100644 index 0000000..20dbc05 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepThreeValidationResult.java @@ -0,0 +1,10 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class BusinessFeasibilityStepThreeValidationResult { + private boolean valid; + private String message; + 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; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationRequest.java new file mode 100644 index 0000000..1e1769f --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationRequest.java @@ -0,0 +1,12 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class BusinessFeasibilityStepTwoValidationRequest { + private String decision; + private List priorities; + public String getDecision() { return decision; } + public void setDecision(String decision) { this.decision = decision; } + public List getPriorities() { return priorities; } + public void setPriorities(List priorities) { this.priorities = priorities; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationResult.java new file mode 100644 index 0000000..1ebce60 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/BusinessFeasibilityStepTwoValidationResult.java @@ -0,0 +1,10 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class BusinessFeasibilityStepTwoValidationResult { + private boolean valid; + private String message; + 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; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepOneService.java b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepOneService.java new file mode 100644 index 0000000..fb5128b --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepOneService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepOneValidationResult; + +public interface BusinessFeasibilityStepOneService { + BusinessFeasibilityStepOneValidationResult validate(BusinessFeasibilityStepOneValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepThreeService.java b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepThreeService.java new file mode 100644 index 0000000..3cfc224 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepThreeService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationResult; + +public interface BusinessFeasibilityStepThreeService { + BusinessFeasibilityStepThreeValidationResult validate(BusinessFeasibilityStepThreeValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepTwoService.java b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepTwoService.java new file mode 100644 index 0000000..904a1d5 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/BusinessFeasibilityStepTwoService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationResult; + +public interface BusinessFeasibilityStepTwoService { + BusinessFeasibilityStepTwoValidationResult validate(BusinessFeasibilityStepTwoValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImpl.java new file mode 100644 index 0000000..4ec8e85 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImpl.java @@ -0,0 +1,102 @@ +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.BusinessFeasibilityStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepOneValidationResult; +import com.sztzjy.linkCommerce.service.BusinessFeasibilityStepOneService; +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.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +@Service +public class BusinessFeasibilityStepOneServiceImpl implements BusinessFeasibilityStepOneService { + private static final List COST_IDS = Arrays.asList("hardware-bom", "manufacturing", "rd-amortization", "channel-marketing", "logistics-warehouse"); + private static final List RISK_TYPES = Arrays.asList("技术风险", "市场风险", "供应链风险", "资金风险"); + private static final Set IMPACTS = new LinkedHashSet<>(Arrays.asList("高", "中", "低")); + + @Override + public BusinessFeasibilityStepOneValidationResult validate(BusinessFeasibilityStepOneValidationRequest request, JwtUser user) { + assertStudent(user); + if (request == null) throw badRequest("请完成商业可行性论证第 1 步"); + validateOverview(request.getProjectOverview()); + validateMarket(request.getMarketAnalysis()); + validateTargetUsers(request.getTargetUsers()); + validateBusinessModels(request.getBusinessModels()); + BigDecimal totalCost = validateAndSumCosts(request.getCosts()); + BigDecimal targetPrice = request.getTargetPrice(); + if (targetPrice == null || targetPrice.compareTo(BigDecimal.ZERO) <= 0) throw badRequest("请填写大于 0 的目标售价"); + if (targetPrice.compareTo(totalCost) < 0) throw badRequest("目标售价不能低于单位总成本"); + validateRisks(request.getRisks()); + + BigDecimal unitGrossProfit = targetPrice.subtract(totalCost).setScale(2, RoundingMode.HALF_UP); + BigDecimal grossMargin = unitGrossProfit.multiply(BigDecimal.valueOf(100)).divide(totalCost, 2, RoundingMode.HALF_UP); + BusinessFeasibilityStepOneValidationResult result = new BusinessFeasibilityStepOneValidationResult(); + result.setValid(true); + result.setMessage("校验通过"); + result.setUnitTotalCost(totalCost.setScale(2, RoundingMode.HALF_UP)); + result.setUnitGrossProfit(unitGrossProfit); + result.setGrossMargin(grossMargin); + return result; + } + + private void assertStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + + private void validateOverview(BusinessFeasibilityStepOneValidationRequest.ProjectOverview overview) { + if (overview == null || isBlank(overview.getProjectBackground()) || isBlank(overview.getProductPositioning()) || isBlank(overview.getProjectGoals()) || isBlank(overview.getMacroEnvironmentConclusion()) || isBlank(overview.getCompetitiveLandscapeConclusion())) throw badRequest("请完整填写项目概述"); + } + + private void validateMarket(BusinessFeasibilityStepOneValidationRequest.MarketAnalysis market) { + if (market == null || invalidPositive(market.getAnnualMarketSize()) || invalidPercentage(market.getMonthlyGrowthRate()) || invalidPercentage(market.getTopBrandMarketShare()) || isBlank(market.getCompetitorsPriceRange()) || invalidPercentage(market.getMarketConcentrationCr3())) throw badRequest("请完整填写市场规模与竞争格局分析"); + } + + private void validateTargetUsers(List users) { + if (users == null || users.size() != 3) throw badRequest("请填写 3 类目标用户群体"); + if (users.stream().anyMatch(user -> user == null || isBlank(user.getUserGroup()) || isBlank(user.getScenario()) || isBlank(user.getCoreNeed()) || isBlank(user.getSpendingPower()))) throw badRequest("请完整填写目标用户群体分析"); + } + + private void validateBusinessModels(List models) { + if (models == null || models.size() != 2) throw badRequest("请填写产品销售渠道和增值服务两项商业模式"); + Set modules = new LinkedHashSet<>(); + for (BusinessFeasibilityStepOneValidationRequest.BusinessModel model : models) { + if (model == null || isBlank(model.getModule()) || isBlank(model.getStrategy()) || isBlank(model.getExplanation()) || !modules.add(StringUtils.trim(model.getModule()))) throw badRequest("请完整填写商业模式梳理"); + } + } + + private BigDecimal validateAndSumCosts(List costs) { + if (costs == null || costs.size() != COST_IDS.size()) throw badRequest("请填写全部成本项目"); + Set submittedIds = new LinkedHashSet<>(); + BigDecimal total = BigDecimal.ZERO; + for (BusinessFeasibilityStepOneValidationRequest.CostItem cost : costs) { + String id = cost == null ? "" : StringUtils.trimToEmpty(cost.getId()); + if (cost == null || !COST_IDS.contains(id) || !submittedIds.add(id) || cost.getAmount() == null || cost.getAmount().compareTo(BigDecimal.ZERO) < 0) throw badRequest("成本项目必须是非负数且不可重复"); + total = total.add(cost.getAmount()); + } + if (!submittedIds.containsAll(COST_IDS) || total.compareTo(BigDecimal.ZERO) <= 0) throw badRequest("单位总成本必须大于 0"); + return total; + } + + private void validateRisks(List risks) { + if (risks == null || risks.size() != RISK_TYPES.size()) throw badRequest("请完整填写 4 类项目风险"); + Set types = new LinkedHashSet<>(); + for (BusinessFeasibilityStepOneValidationRequest.Risk risk : risks) { + String type = risk == null ? "" : StringUtils.trimToEmpty(risk.getType()); + if (!RISK_TYPES.contains(type) || !types.add(type) || isBlank(risk.getDescription()) || !IMPACTS.contains(StringUtils.trimToEmpty(risk.getImpact())) || isBlank(risk.getResponse())) throw badRequest("请完整填写风险描述、影响程度和应对措施"); + } + } + + private boolean invalidPositive(BigDecimal value) { return value == null || value.compareTo(BigDecimal.ZERO) <= 0; } + private boolean invalidPercentage(BigDecimal value) { return value == null || value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(BigDecimal.valueOf(100)) > 0; } + private boolean isBlank(String value) { return StringUtils.isBlank(value); } + private ServiceException badRequest(String message) { return new ServiceException(HttpStatus.BAD_REQUEST, message); } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepThreeServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepThreeServiceImpl.java new file mode 100644 index 0000000..89fcc58 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepThreeServiceImpl.java @@ -0,0 +1,38 @@ +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.StudentTrainingAnswer; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationResult; +import com.sztzjy.linkCommerce.service.BusinessFeasibilityStepThreeService; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.Locale; + +@Service +public class BusinessFeasibilityStepThreeServiceImpl implements BusinessFeasibilityStepThreeService { + private static final String TASK_KEY = "business-feasibility"; + @Autowired private StudentTrainingAnswerService studentTrainingAnswerService; + + @Override + public BusinessFeasibilityStepThreeValidationResult validate(BusinessFeasibilityStepThreeValidationRequest request, JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再提交"); + if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可提交实训报告"); + StudentTrainingAnswer saved = studentTrainingAnswerService.get(TASK_KEY, null, user); + if (saved == null || StringUtils.isBlank(saved.getStep1Answer()) || StringUtils.isBlank(saved.getStep2Answer())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存前两步商业可行性论证"); + String url = request == null ? "" : StringUtils.trimToEmpty(request.getReportUrl()); + String name = request == null ? "" : StringUtils.trimToEmpty(request.getReportName()); + if (StringUtils.isBlank(url) || !isSupported(url, name)) throw new ServiceException(HttpStatus.BAD_REQUEST, "请上传 PDF、Word、PNG 或 JPG 格式的可行性论证报告"); + BusinessFeasibilityStepThreeValidationResult result = new BusinessFeasibilityStepThreeValidationResult(); result.setValid(true); result.setMessage("校验通过"); return result; + } + + private boolean isSupported(String url, String name) { + String value = (url + " " + name).toLowerCase(Locale.ROOT); + return value.matches(".*\\.(pdf|doc|docx|png|jpg|jpeg)(?:[?\\s].*)?"); + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepTwoServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepTwoServiceImpl.java new file mode 100644 index 0000000..ebb96b3 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepTwoServiceImpl.java @@ -0,0 +1,40 @@ +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.StudentTrainingAnswer; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationResult; +import com.sztzjy.linkCommerce.service.BusinessFeasibilityStepTwoService; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +@Service +public class BusinessFeasibilityStepTwoServiceImpl implements BusinessFeasibilityStepTwoService { + private static final String TASK_KEY = "business-feasibility"; + private static final Set DECISIONS = new LinkedHashSet<>(Arrays.asList("highly-feasible", "feasible-after-optimization", "observe", "not-feasible")); + private static final Set PRIORITIES = new LinkedHashSet<>(Arrays.asList("hardware-cost-control", "content-ecosystem", "channel-expansion", "brand-differentiation", "supply-chain-management", "capital-planning")); + @Autowired private StudentTrainingAnswerService studentTrainingAnswerService; + + @Override + public BusinessFeasibilityStepTwoValidationResult validate(BusinessFeasibilityStepTwoValidationRequest request, JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + StudentTrainingAnswer saved = studentTrainingAnswerService.get(TASK_KEY, null, user); + if (saved == null || StringUtils.isBlank(saved.getStep1Answer())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 1 步商业可行性论证"); + String decision = request == null ? "" : StringUtils.trimToEmpty(request.getDecision()); + if (!DECISIONS.contains(decision)) throw new ServiceException(HttpStatus.BAD_REQUEST, "请选择项目商业可行性总体评估"); + Set selected = new LinkedHashSet<>(); + if (request.getPriorities() != null) for (String priority : request.getPriorities()) { String value = StringUtils.trimToEmpty(priority); if (!PRIORITIES.contains(value) || !selected.add(value)) throw new ServiceException(HttpStatus.BAD_REQUEST, "优先问题包含无效或重复项"); } + if ("not-feasible".equals(decision)) { if (!selected.isEmpty()) throw new ServiceException(HttpStatus.BAD_REQUEST, "选择不可行时无需勾选优先问题"); } + else if (selected.size() != 3) throw new ServiceException(HttpStatus.BAD_REQUEST, "请且仅选择 3 个需要优先解决的关键问题"); + BusinessFeasibilityStepTwoValidationResult result = new BusinessFeasibilityStepTwoValidationResult(); result.setValid(true); result.setMessage("校验通过"); return result; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImplTest.java new file mode 100644 index 0000000..cbc212d --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepOneServiceImplTest.java @@ -0,0 +1,55 @@ +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.BusinessFeasibilityStepOneValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepOneValidationResult; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +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 BusinessFeasibilityStepOneServiceImplTest { + @Test + void validatesCompletedAnalysisAndRecalculatesProfit() { + BusinessFeasibilityStepOneValidationResult result = new BusinessFeasibilityStepOneServiceImpl().validate(completedRequest(), student()); + assertTrue(result.isValid()); + assertEquals(new BigDecimal("100.00"), result.getUnitTotalCost()); + assertEquals(new BigDecimal("99.00"), result.getUnitGrossProfit()); + assertEquals(new BigDecimal("99.00"), result.getGrossMargin()); + } + + @Test + void rejectsIncompleteRiskAndLossMakingPrice() { + BusinessFeasibilityStepOneValidationRequest incompleteRisk = completedRequest(); + incompleteRisk.getRisks().get(0).setResponse(""); + assertThrows(ServiceException.class, () -> new BusinessFeasibilityStepOneServiceImpl().validate(incompleteRisk, student())); + BusinessFeasibilityStepOneValidationRequest lossMaking = completedRequest(); + lossMaking.setTargetPrice(new BigDecimal("99")); + assertThrows(ServiceException.class, () -> new BusinessFeasibilityStepOneServiceImpl().validate(lossMaking, student())); + } + + private BusinessFeasibilityStepOneValidationRequest completedRequest() { + BusinessFeasibilityStepOneValidationRequest request = new BusinessFeasibilityStepOneValidationRequest(); + BusinessFeasibilityStepOneValidationRequest.ProjectOverview overview = new BusinessFeasibilityStepOneValidationRequest.ProjectOverview(); + overview.setProjectBackground("背景"); overview.setProductPositioning("定位"); overview.setProjectGoals("目标"); overview.setMacroEnvironmentConclusion("PEST 结论"); overview.setCompetitiveLandscapeConclusion("SWOT 结论"); request.setProjectOverview(overview); + BusinessFeasibilityStepOneValidationRequest.MarketAnalysis market = new BusinessFeasibilityStepOneValidationRequest.MarketAnalysis(); + market.setAnnualMarketSize(new BigDecimal("12")); market.setMonthlyGrowthRate(new BigDecimal("3")); market.setTopBrandMarketShare(new BigDecimal("28")); market.setCompetitorsPriceRange("199-499"); market.setMarketConcentrationCr3(new BigDecimal("55")); request.setMarketAnalysis(market); + request.setTargetUsers(Arrays.asList(targetUser(), targetUser(), targetUser())); + request.setBusinessModels(Arrays.asList(model("产品销售渠道"), model("增值服务"))); + ArrayList costs = new ArrayList<>(); + Arrays.asList("hardware-bom", "manufacturing", "rd-amortization", "channel-marketing", "logistics-warehouse").forEach(id -> { BusinessFeasibilityStepOneValidationRequest.CostItem item = new BusinessFeasibilityStepOneValidationRequest.CostItem(); item.setId(id); item.setAmount(new BigDecimal("20")); costs.add(item); }); + request.setCosts(costs); request.setTargetPrice(new BigDecimal("199")); + request.setRisks(Arrays.asList(risk("技术风险"), risk("市场风险"), risk("供应链风险"), risk("资金风险"))); + return request; + } + private BusinessFeasibilityStepOneValidationRequest.TargetUser targetUser() { BusinessFeasibilityStepOneValidationRequest.TargetUser value = new BusinessFeasibilityStepOneValidationRequest.TargetUser(); value.setUserGroup("用户"); value.setScenario("场景"); value.setCoreNeed("需求"); value.setSpendingPower("中"); return value; } + private BusinessFeasibilityStepOneValidationRequest.BusinessModel model(String module) { BusinessFeasibilityStepOneValidationRequest.BusinessModel value = new BusinessFeasibilityStepOneValidationRequest.BusinessModel(); value.setModule(module); value.setStrategy("策略"); value.setExplanation("说明"); return value; } + private BusinessFeasibilityStepOneValidationRequest.Risk risk(String type) { BusinessFeasibilityStepOneValidationRequest.Risk value = new BusinessFeasibilityStepOneValidationRequest.Risk(); value.setType(type); value.setDescription("风险描述"); value.setImpact("高"); value.setResponse("应对措施"); return value; } + 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/BusinessFeasibilityStepThreeServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepThreeServiceImplTest.java new file mode 100644 index 0000000..1e448ea --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepThreeServiceImplTest.java @@ -0,0 +1,35 @@ +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.StudentTrainingAnswer; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepThreeValidationRequest; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BusinessFeasibilityStepThreeServiceImplTest { + @Test + void validatesUploadedReportAfterFirstTwoSteps() { + BusinessFeasibilityStepThreeValidationRequest request = request("/profile/upload/feasibility-report.docx", "feasibility-report.docx"); + assertTrue(serviceWithFirstTwoSteps().validate(request, student()).isValid()); + } + + @Test + void rejectsUnsupportedReportAndMissingPrerequisite() { + assertThrows(ServiceException.class, () -> serviceWithFirstTwoSteps().validate(request("/profile/upload/report.zip", "report.zip"), student())); + BusinessFeasibilityStepThreeServiceImpl service = new BusinessFeasibilityStepThreeServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(new StudentTrainingAnswer()); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + assertThrows(ServiceException.class, () -> service.validate(request("/profile/upload/report.pdf", "report.pdf"), student())); + } + + private BusinessFeasibilityStepThreeServiceImpl serviceWithFirstTwoSteps() { BusinessFeasibilityStepThreeServiceImpl service = new BusinessFeasibilityStepThreeServiceImpl(); StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep1Answer("{}"); answer.setStep2Answer("{}"); when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); return service; } + private BusinessFeasibilityStepThreeValidationRequest request(String url, String name) { BusinessFeasibilityStepThreeValidationRequest request = new BusinessFeasibilityStepThreeValidationRequest(); request.setReportUrl(url); request.setReportName(name); 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/BusinessFeasibilityStepTwoServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepTwoServiceImplTest.java new file mode 100644 index 0000000..3df45e5 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/BusinessFeasibilityStepTwoServiceImplTest.java @@ -0,0 +1,40 @@ +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.StudentTrainingAnswer; +import com.sztzjy.linkCommerce.entity.dto.BusinessFeasibilityStepTwoValidationRequest; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BusinessFeasibilityStepTwoServiceImplTest { + @Test + void validatesThreePrioritiesForProjectDecision() { + assertTrue(serviceWithStepOne().validate(request("feasible-after-optimization", "hardware-cost-control", "channel-expansion", "capital-planning"), student()).isValid()); + } + + @Test + void acceptsNoPriorityForNotFeasibleAndRejectsOtherCounts() { + assertTrue(serviceWithStepOne().validate(request("not-feasible"), student()).isValid()); + assertThrows(ServiceException.class, () -> serviceWithStepOne().validate(request("observe", "hardware-cost-control", "channel-expansion"), student())); + assertThrows(ServiceException.class, () -> serviceWithStepOne().validate(request("not-feasible", "hardware-cost-control"), student())); + } + + private BusinessFeasibilityStepTwoServiceImpl serviceWithStepOne() { + BusinessFeasibilityStepTwoServiceImpl service = new BusinessFeasibilityStepTwoServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); StudentTrainingAnswer answer = new StudentTrainingAnswer(); answer.setStep1Answer("{\"form\":{}}"); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); return service; + } + private BusinessFeasibilityStepTwoValidationRequest request(String decision, String... priorities) { BusinessFeasibilityStepTwoValidationRequest request = new BusinessFeasibilityStepTwoValidationRequest(); request.setDecision(decision); request.setPriorities(Arrays.asList(priorities)); return request; } + private JwtUser student() { JwtUser user = new JwtUser(); user.setUserId("stu-1"); user.setRoleId(4); return user; } +}