feat(quotation): 添加随机配件和交机服务功能

- 在PDF模板中添加表格样式和随机配件、交机服务表格展示
- 新增备件总价字段到购物车配件实体类
- 设置购物车VO默认系数为BigDecimal.ONE
- 调整价格计算逻辑,添加折扣率处理
- 保存购物车数据到数据库
- 添加币种和汇率一致性校验
- 更新PDF导出功能的文档设置方法
- 为配件和服务请求类添加数据验证注解
- 设置配件费和服务费默认值为零
- 修改币种验证从NotBlank为NotNull
- 为配件和服务列表添加@Valid注解进行嵌套验证
This commit is contained in:
曹鹏飞 2026-03-17 10:15:48 +08:00
parent 9a5fa93957
commit 531b3b2932
7 changed files with 95 additions and 27 deletions

View File

@ -173,20 +173,22 @@ public class ShoppingController extends ControllerBase {
.setTotalFee(modelPrice.getAmount())
.setActualFee(modelPrice.getAmount());
log.debug("机型【{}】售价为{}", request.getModelNo(), modelPrice);
//系数
Pair<BigDecimal, BigDecimal> pair = getRatio(request.getModelId());
BigDecimal standardRatio = pair.getLeft(), optionalRatio = pair.getRight(), discountRatio = BigDecimal.ONE;
log.debug("机型【{}】标准配件系数为{},可选配件系数为{}", request.getModelNo(), standardRatio, optionalRatio);
if (AppUserUtil.isAgent()) {
//代理商
QuotationDiscountDTO discountDTO = discountService.getEffectiveForCustomer(request.getModelId(), request.getTargetId(), categoryId);
if (Objects.nonNull(discountDTO)) {
vo.setDiscountId(discountDTO.getDiscountId());
vo.setActualFee(vo.getTotalFee().multiply(discountDTO.getRatio()));
vo.setDiscount(vo.getTotalFee().subtract(vo.getActualFee()));
log.debug("机型【{}】打折后价格为{},优惠{}", request.getModelNo(), vo.getActualFee(), vo.getDiscount());
// vo.setActualFee(vo.getTotalFee().multiply(discountDTO.getRatio()));
// vo.setDiscount(vo.getTotalFee().subtract(vo.getActualFee()));
// log.debug("机型【{}】打折后价格为{},优惠{}", request.getModelNo(), vo.getActualFee(), vo.getDiscount());
discountRatio = discountDTO.getRatio();
log.debug("机型【{}】折扣为{}", request.getModelNo(), discountRatio);
}
}
//系数
Pair<BigDecimal, BigDecimal> pair = getRatio(request.getModelId());
BigDecimal standardRatio = pair.getLeft(), optionalRatio = pair.getRight();
log.debug("机型【{}】标准配件系数为{},可选配件系数为{}", request.getModelNo(), standardRatio, optionalRatio);
if (!request.getShowLowestPrice()) {
//方案
QuotationUserPlanModelItem planModelItem = userPlanModelItemService.getEffectiveForUser(request.getModelId(), AppUserUtil.isAgent() ? 1 : 0, AppUserUtil.getUserId());
@ -198,13 +200,16 @@ public class ShoppingController extends ControllerBase {
} else {
QuotationUserPlanDefault userPlanDefault = userPlanDefaultService.getEffectiveForUser(AppUserUtil.isAgent() ? 1 : 0, AppUserUtil.getUserId());
if (Objects.nonNull(userPlanDefault)) {
vo.setDefaultRatio(userPlanDefault.getRatio());
log.debug("用户方案默认系数为{}", userPlanDefault.getRatio());
standardRatio = NumberUtil.multiply(standardRatio, userPlanDefault.getRatio());
log.debug("机型【{}】标准配件系数为{},可选配件系数为{}", request.getModelNo(), standardRatio, optionalRatio);
}
}
}
vo.setActualFee(vo.getTotalFee().subtract(vo.getDiscount()).multiply(standardRatio));
vo.setTotalFee(NumberUtil.multiply(vo.getTotalFee(), standardRatio));
vo.setActualFee(NumberUtil.multiply(vo.getTotalFee(), discountRatio));
vo.setDiscount(vo.getTotalFee().subtract(vo.getActualFee()));
log.debug("机型【{}】价格为{},优惠{}", request.getModelNo(), vo.getActualFee(), vo.getDiscount());
//获取部件配置
List<ModelConfigEffectiveDTO> parts = modelConfigService.getEffectives(modelPrice.getPriceId(), categoryId, MultilingualUtil.getLanguage());
@ -305,6 +310,7 @@ public class ShoppingController extends ControllerBase {
).collect(Collectors.toList())
);
}
shoppingCartService.save(cart);
return ApiResult.success(cartId);
}
@ -368,6 +374,10 @@ public class ShoppingController extends ControllerBase {
.throwMessage("客户名称不一致");
VUtils.trueThrowBusinessError(carts.stream().map(QuotationShoppingCart::getTargetId).collect(Collectors.toSet()).size() > 1)
.throwMessage("报价对象不一致");
VUtils.trueThrowBusinessError(carts.stream().map(QuotationShoppingCart::getCurrency).collect(Collectors.toSet()).size() > 1)
.throwMessage("币种不一致");
VUtils.trueThrowBusinessError(carts.stream().map(QuotationShoppingCart::getExchangeRate).collect(Collectors.toSet()).size() > 1)
.throwMessage("汇率不一致");
QuotationShoppingOrder order = new QuotationShoppingOrder()
.setCreateByType(AppUserUtil.isAgent() ? 1 : 0)
.setCreateBy(AppUserUtil.getUserName())
@ -538,23 +548,22 @@ public class ShoppingController extends ControllerBase {
// * 报价单-复制报价单
// */
// @PostMapping("/quotation/copy")
// public ApiResult<Void> copyQuotation(@Valid @RequestBody QuotationCopyRequest request){
// public ApiResult<Void> copyQuotation(@Valid @RequestBody QuotationCopyRequest request) {
// QuotationShoppingOrder order = shoppingOrderService.getById(request.getId());
// VUtils.trueThrowBusinessError(Objects.isNull(order)).throwMessage("未找到报价单");
// List<QuotationShoppingOrderItem> orderItems = shoppingOrderItemService.lambdaQuery()
// .eq(QuotationShoppingOrderItem::getOrderId, request.getId())
// .list();
// List<QuotationShoppingCart> carts = shoppingCartService.lambdaQuery()
// .in(QuotationShoppingCart::getId, orderItems.stream().map(QuotationShoppingOrderItem::getCartId).collect(Collectors.toList()))
// .list();
// VUtils.trueThrowBusinessError(orderItems.size() > 1).throwMessage("多机型报价单不能复制");
// QuotationShoppingCart cart = shoppingCartService.getById(orderItems.get(0).getCartId());
// ShoppingCartVO vo = new ShoppingCartVO()
// .setModelId(request.getModelId())
// .setModelId(cart.getModelId())
// .setTargetId(request.getTargetId())
// .setCustomerName(request.getCustomerName())
// .setPriceId(modelPrice.getPriceId())
// .setConfigId(modelPrice.getConfigId())
// .setTotalFee(modelPrice.getAmount())
// .setActualFee(modelPrice.getAmount());
// .setPriceId(cart.getPriceId())
// .setConfigId(cart.getConfigId())
// .setTotalFee(cart.getAmount())
// .setActualFee(cart.getAmount());
// log.debug("机型【{}】售价为{}", request.getModelNo(), modelPrice);
// if (AppUserUtil.isAgent()) {
// //代理商
@ -709,7 +718,7 @@ public class ShoppingController extends ControllerBase {
* 报价单-导出PDF
*/
@GetMapping("/quotation/exportToPdf")
public void exportToPdf(HttpServletResponse response, @RequestParam @NotNull(message = "报价单id不能为空") Long id){
public void exportToPdf(HttpServletResponse response, @RequestParam @NotNull(message = "报价单id不能为空") Long id) {
// QuotationShoppingOrder order = shoppingOrderService.getById(id);
// VUtils.trueThrowBusinessError(Objects.isNull(order)).throwMessage("未找到报价单");
Map<String, Object> order = new HashMap<>();
@ -735,7 +744,7 @@ public class ShoppingController extends ControllerBase {
ITextRenderer renderer = new ITextRenderer();
renderer.getFontResolver().addFont("fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
URL baseUrl = new ClassPathResource("templates/").getURL();
renderer.setDocumentFromString(html,baseUrl.toString());
renderer.setDocumentFromString(html, baseUrl.toString());
renderer.layout();
try (OutputStream outputStream = response.getOutputStream()) {
renderer.createPDF(outputStream);

View File

@ -2,6 +2,8 @@ package com.nflg.mobilebroken.quotation.pojo.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@ -10,6 +12,7 @@ public class ShoppingSaveAccessoryRequest {
/**
* 物料编号
*/
@NotBlank
private String materialNo;
/**
@ -20,15 +23,18 @@ public class ShoppingSaveAccessoryRequest {
/**
* 备件数量
*/
@NotNull
private Integer num;
/**
* 备件单价
*/
@NotNull
private BigDecimal amount;
/**
* 备件总价
*/
@NotNull
private BigDecimal totalAmount;
}

View File

@ -1,10 +1,13 @@
package com.nflg.mobilebroken.quotation.pojo.request;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.math.BigDecimal;
import java.util.List;
@ -100,14 +103,12 @@ public class ShoppingSaveRequest {
/**
* 随机配件总价
*/
@NotNull
private BigDecimal accessoryFee;
private BigDecimal accessoryFee = BigDecimal.ZERO;
/**
* 交机服务总价
*/
@NotNull
private BigDecimal serviceFee;
private BigDecimal serviceFee = BigDecimal.ZERO;
/**
* 交货方式字典id
@ -147,7 +148,7 @@ public class ShoppingSaveRequest {
/**
* 币种字典id
*/
@NotBlank
@NotNull
private Long currency;
/**
@ -169,10 +170,12 @@ public class ShoppingSaveRequest {
/**
* 随机配件
*/
@Valid
private List<ShoppingSaveAccessoryRequest> accessories;
/**
* 交机服务
*/
@Valid
private List<ShoppingSaveServiceRequest> services;
}

View File

@ -2,6 +2,7 @@ package com.nflg.mobilebroken.quotation.pojo.request;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@ -10,20 +11,24 @@ public class ShoppingSaveServiceRequest {
/**
* 工程师人数
*/
@NotNull
private Integer userNum;
/**
* 服务天数
*/
@NotNull
private Integer days;
/**
* 单人天费用
*/
@NotNull
private BigDecimal fee;
/**
* 服务总费用
*/
@NotNull
private BigDecimal totalFee;
}

View File

@ -41,7 +41,7 @@ public class ShoppingCartVO {
/**
* 默认系数没有方案时使用
*/
private BigDecimal defaultRatio;
private BigDecimal defaultRatio = BigDecimal.ONE;
/**
* 客户名称

View File

@ -44,6 +44,16 @@
.data1 {
width: 100%;
}
table, th, td {
border: black 1px solid;
border-collapse: collapse;
padding: 5px;
}
table {
width: 100%;
}
</style>
</head>
<body>
@ -129,8 +139,38 @@
<div th:text="${info.no}">其他配置</div>
<div th:text="${info.no}">油漆要求</div>
<div th:text="${info.no}">其它要求说明</div>
<div th:text="${info.no}">其它要求费用</div>
<div th:text="${info.no}">随机配件</div>
<table>
<tr>
<th>物料编号</th>
<th>备件名称</th>
<th>备件数量</th>
<th>备件单价</th>
<th>备件总金额</th>
</tr>
<tr th:each="item, itemStat : ${measure.items}" th:if="${not #lists.isEmpty(measure.items)}">
<td th:text="${itemStat.index + 1}"></td>
<td th:text="${item.name}"></td>
<td th:text="${item.superintendent}"></td>
<td th:text="${item.scheduleDate}"></td>
<td th:text="${item.confirmedDate}"></td>
</tr>
</table>
<div th:text="${info.no}">交机服务</div>
<table>
<tr>
<th>工程师人数</th>
<th>服务天数</th>
<th>单人天费用</th>
<th>服务总费用</th>
</tr>
<tr th:each="item, itemStat : ${measure.items}" th:if="${not #lists.isEmpty(measure.items)}">
<td th:text="${itemStat.index + 1}"></td>
<td th:text="${item.name}"></td>
<td th:text="${item.superintendent}"></td>
<td th:text="${item.scheduleDate}"></td>
</tr>
</table>
</div>
</body>
</html>

View File

@ -50,6 +50,11 @@ public class QuotationShoppingCartAccessory implements Serializable {
*/
private BigDecimal amount;
/**
* 备件总价
*/
private BigDecimal totalAmount;
/**
* 优惠金额
*/