feat(quotation): 新增调价记录查询功能和优化报价单管理

- 在AdminShoppingController中新增getAdjusts接口用于获取调价记录
- 添加QuotationShoppingOrderAdjust实体和相关服务注入
- 重构AppRatioAgentConfigController中的代理配置逻辑
- 优化DiscountConfigController中的报价对象查询功能
- 修复ForbidConfigController中的禁售配置逻辑错误
- 新增DiscountApplyForVO数据传输对象并调整字段结构
- 添加PDF模板文件用于报价单导出功能
- 优化产品型号搜索和详情查询的数据映射
- 新增QuotationCopyRequest用于报价单复制功能
- 重构购物车控制器中的用户查询逻辑
- 添加购物车数量统计接口
- 优化数据库查询映射和字段命名规范
This commit is contained in:
曹鹏飞 2026-03-16 17:15:02 +08:00
parent e12dd45cd4
commit 442355700a
24 changed files with 626 additions and 65 deletions

View File

@ -10,6 +10,11 @@ public class ProductTypeSimpleVO {
*/
private String moduleName;
/**
* 系列ID
*/
private Long seriesNumber;
/**
* 系列名称
*/
@ -28,5 +33,5 @@ public class ProductTypeSimpleVO {
/**
* 批次号
*/
private String batchNumber;
private String typeNumber;
}

View File

@ -20,6 +20,11 @@ public class QuotationProductModelInfoVO {
*/
private String typeName;
/**
* 批次编号
*/
private Long batchNumber;
/**
* 机型
*/

View File

@ -55,4 +55,9 @@ public class QuotationProductModelSearchVO {
*/
@JsonProperty("hasDiscount")
private Boolean hasDiscount;
/**
* 机型图片
*/
private String image;
}

View File

@ -12,14 +12,13 @@ import com.nflg.mobilebroken.common.util.AppUserUtil;
import com.nflg.mobilebroken.quotation.controller.ControllerBase;
import com.nflg.mobilebroken.repository.entity.AdminUser;
import com.nflg.mobilebroken.repository.entity.ProductModel;
import com.nflg.mobilebroken.repository.entity.QuotationShoppingOrderAdjust;
import com.nflg.mobilebroken.repository.entity.TBaseCustomer;
import com.nflg.mobilebroken.repository.service.IAdminUserService;
import com.nflg.mobilebroken.repository.service.IQuotationShoppingOrderAdjustService;
import com.nflg.mobilebroken.repository.service.IQuotationShoppingOrderService;
import com.nflg.mobilebroken.repository.service.ITBaseCustomerService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
@ -36,13 +35,16 @@ public class AdminShoppingController extends ControllerBase {
@Resource
private IQuotationShoppingOrderService shoppingOrderService;
@Resource
private IQuotationShoppingOrderAdjustService shoppingOrderAdjustService;
/**
* 查询报价单
*/
@PostMapping("/search")
public ApiResult<PageData<QuotationSearchVO>> searchQuotation(@Valid @RequestBody QuotationAdminSearchRequest request) {
IPage<QuotationSearchVO> datas = shoppingOrderService.searchFromAdmin(request);
if (CollectionUtil.isEmpty(datas.getRecords())){
if (CollectionUtil.isEmpty(datas.getRecords())) {
return ApiResult.success(PageData.empty());
}
return ApiResult.success(datas, data -> {
@ -50,4 +52,15 @@ public class AdminShoppingController extends ControllerBase {
return data;
});
}
/**
* 获取调价记录
*/
@GetMapping("/adjusts")
public ApiResult<List<QuotationShoppingOrderAdjust>> getAdjusts(@RequestParam Long orderId) {
return ApiResult.success(shoppingOrderAdjustService.lambdaQuery()
.eq(QuotationShoppingOrderAdjust::getOrderId, orderId)
.list()
);
}
}

View File

@ -80,6 +80,12 @@ public class DiscountConfigController extends ControllerBase {
@Resource
private IProductModelService productModelService;
@Resource
private IAppUserService appUserService;
@Resource
private ITBaseAreaService areaService;
/**
* 根据机型获取价格
*/
@ -132,6 +138,61 @@ public class DiscountConfigController extends ControllerBase {
return ApiResult.success(vos);
}
/**
* 获取所有报价对象
*/
@GetMapping("/applyFors")
public ApiResult<List<DiscountApplyForVO>> getApplyFors() {
List<DiscountApplyForVO> applyFors = new ArrayList<>();
List<DictionaryItem> areas = dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DIRECT_SALES_CATEGORY);
List<AppUser> appUsers = appUserService.getEndUsers();
if (CollectionUtil.isNotEmpty(appUsers)) {
List<TBaseArea> baseAreas = areaService.lambdaQuery()
.in(TBaseArea::getId, appUsers.stream().map(AppUser::getAreaId).collect(Collectors.toSet()))
.list();
appUsers.forEach(appUser -> {
applyFors.add(new DiscountApplyForVO()
.setSourceType(0)
.setSourceId(appUser.getId())
.setName(appUser.getName())
.setAreaName(
areas.stream()
.filter(area -> Objects.equals(area.getId()
, baseAreas.stream()
.filter(ba -> ba.getId().equals(appUser.getAreaId()))
.findFirst()
.map(TBaseArea::getCategoryId)
.orElse(null)
)
)
.findFirst()
.map(DictionaryItem::getName)
.orElse("未知")
)
);
});
}
List<TBaseCustomer> customers = customerService.getForQuotation();
if (CollectionUtil.isNotEmpty(customers)) {
applyFors.addAll(customers.stream()
.map(c -> new DiscountApplyForVO()
.setSourceType(1)
.setSourceId(c.getId())
.setName(c.getAgencyCompanyName())
.setAreaName(
areas.stream()
.filter(area -> area.getId().equals(c.getCategoryId()))
.findFirst()
.map(DictionaryItem::getName)
.orElse("未知")
)
)
.collect(Collectors.toList())
);
}
return ApiResult.success(applyFors);
}
/**
* 搜索
*/
@ -150,7 +211,6 @@ public class DiscountConfigController extends ControllerBase {
.in(QuotationModelDiscountArea::getDiscountId, pdata.getRecords().stream().map(ModelDiscountConfigVO::getId).collect(Collectors.toList()))
.list();
List<QuotationModelDiscountApply> applies = discountApplyService.lambdaQuery()
.eq(QuotationModelDiscountApply::getSourceType, 1)
.in(QuotationModelDiscountApply::getDiscountId, pdata.getRecords().stream().map(ModelDiscountConfigVO::getId).collect(Collectors.toList()))
.list();
return ApiResult.success(pdata, data -> {
@ -173,17 +233,51 @@ public class DiscountConfigController extends ControllerBase {
map.put(area.getCode() + "_end", DateTimeUtil.format(Optional.ofNullable(discountArea).map(QuotationModelDiscountArea::getDiscountEndDate).orElse(null), "yyyy-MM-dd"));
map.put(area.getCode() + "_days", Optional.ofNullable(discountArea).map(QuotationModelDiscountArea::getDays).orElse(null));
});
List<TBaseCustomer> customers = customerService.lambdaQuery()
.in(TBaseCustomer::getId, applies.stream()
.filter(apply -> apply.getDiscountId().equals(data.getId()))
List<Integer> applyIds = applies.stream()
.filter(apply -> apply.getSourceType() == 0 && apply.getDiscountId().equals(data.getId()))
.map(QuotationModelDiscountApply::getSourceId)
.collect(Collectors.toList())
)
.collect(Collectors.toList());
List<DiscountApplyForVO> applyFors = new ArrayList<>();
if (CollectionUtil.isNotEmpty(applyIds)) {
List<AppUser> appUsers = appUserService.listByIds(applyIds);
List<TBaseArea> baseAreas = areaService.lambdaQuery()
.in(TBaseArea::getId, appUsers.stream().map(AppUser::getAreaId).collect(Collectors.toSet()))
.list();
map.put("applyFors", customers.stream()
appUsers.forEach(appUser -> {
applyFors.add(new DiscountApplyForVO()
.setSourceType(0)
.setSourceId(appUser.getId())
.setName(appUser.getName())
.setAreaName(
areas.stream()
.filter(area -> Objects.equals(area.getId()
, baseAreas.stream()
.filter(ba -> ba.getId().equals(appUser.getAreaId()))
.findFirst()
.map(TBaseArea::getCategoryId)
.orElse(null)
)
)
.findFirst()
.map(DictionaryItem::getName)
.orElse("未知")
)
);
});
}
applyIds = applies.stream()
.filter(apply -> apply.getSourceType() == 1 && apply.getDiscountId().equals(data.getId()))
.map(QuotationModelDiscountApply::getSourceId)
.collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(applyIds)) {
List<TBaseCustomer> customers = customerService.lambdaQuery()
.in(TBaseCustomer::getId, applyIds)
.list();
applyFors.addAll(customers.stream()
.map(c -> new DiscountApplyForVO()
.setId(c.getId())
.setAgencyCompanyName(c.getAgencyCompanyName())
.setSourceType(1)
.setSourceId(c.getId())
.setName(c.getAgencyCompanyName())
.setAreaName(
areas.stream()
.filter(area -> area.getId().equals(c.getCategoryId()))
@ -191,8 +285,11 @@ public class DiscountConfigController extends ControllerBase {
.map(DictionaryItem::getName)
.orElse("未知")
)
).collect(Collectors.toList())
)
.collect(Collectors.toList())
);
}
map.put("applyFors", applyFors);
return map;
});
}
@ -233,7 +330,8 @@ public class DiscountConfigController extends ControllerBase {
for (JsonNode apply : data.get("applyFors")) {
discountApplies.add(new QuotationModelDiscountApply()
.setDiscountId(discount.getId())
.setSourceId(apply.get("id").asInt())
.setSourceType(apply.get("sourceType").asInt())
.setSourceId(apply.get("sourceId").asInt())
.setCreateById(AdminUserUtil.getUserId())
.setCreateBy(AdminUserUtil.getUserName())
.setCreateTime(LocalDateTime.now())

View File

@ -99,7 +99,7 @@ public class ForbidConfigController extends ControllerBase {
map.put("modelId", data.getBatchNumber());
map.put("modelNo", data.getNo());
customers.forEach(customer -> {
map.put(customer.getAgencyCompanyCode(), forbids.stream()
map.put(customer.getAgencyCompanyCode(), !forbids.stream()
.filter(forbid -> forbid.getModelId().equals(data.getBatchNumber())
&& forbid.getSourceType().equals(1)
&& forbid.getSourceId().equals(customer.getId()))
@ -142,7 +142,7 @@ public class ForbidConfigController extends ControllerBase {
.findFirst()
.orElse(null);
if (Objects.nonNull(customer)) {
Boolean isForbid = (Boolean) value;
Boolean isForbid = !(Boolean)value;
QuotationModelForbid forbid = forbids.stream()
.filter(f -> f.getModelId().equals(modelId) && f.getSourceType() == 1 && f.getSourceId().equals(customer.getId()))
.findFirst()

View File

@ -251,6 +251,7 @@ public class AppRatioAgentConfigController extends ControllerBase {
DictionaryItem category = appUserService.getCategory(cuser);
VUtils.trueThrowBusinessError(Objects.isNull(category)).throwMessage("请联系管理员为你设置区域类型");
List<QuotationUserPlanModelItemDTO> plans = planModelItemService.getEffectivesForAgent();
List<QuotationModelRatioAgentItemDTO> items = ratioAgentItemService.getEffectiveByCreate(AppUserUtil.getUserId());
if (Objects.isNull(request.getUserId())) {
List<RatioConfigSearchVO> vos = new ArrayList<>();
IPage<ModelPriceConfigVO> pdatas = priceService.search(request);
@ -258,8 +259,10 @@ public class AppRatioAgentConfigController extends ControllerBase {
pdatas.getRecords().forEach(data -> {
RatioConfigSearchVO vo = new RatioConfigSearchVO().setModelNo(data.getModelNo());
vos.add(vo);
List<QuotationModelRatioAgentItemDTO> items = ratioAgentItemService.getEffectiveByCreate(AppUserUtil.getUserId());
if (CollectionUtil.isNotEmpty(items)) {
List<QuotationModelRatioAgentItemDTO> modelItems= items.stream()
.filter(it -> it.getModelId().equals(data.getModelId()))
.collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(modelItems)) {
ModelPriceVO modelPrice = prices.stream()
.filter(it -> it.getModelId().equals(data.getModelId()) && it.getAreaId().equals(category.getId()))
.findFirst()
@ -270,7 +273,7 @@ public class AppRatioAgentConfigController extends ControllerBase {
.map(QuotationModelRatioAgentItem::getStandardRatio)
.orElse(null))
: null;
items.forEach(item -> {
modelItems.forEach(item -> {
RatioConfigSearchVO vou = new RatioConfigSearchVO()
.setName(item.getUserName())
.setStandardPrice(basePrice)
@ -301,7 +304,6 @@ public class AppRatioAgentConfigController extends ControllerBase {
pageData.setPageSize(request.getPageSize());
List<RatioConfigSearchVO> vos = new ArrayList<>();
pageData.setItems(vos);
List<QuotationModelRatioAgentItemDTO> items = ratioAgentItemService.getEffectiveByCreate(AppUserUtil.getUserId());
List<QuotationModelRatioAgentItemDTO> filterItems = items.stream()
.filter(item -> item.getUserId().equals(request.getUserId()))
.collect(Collectors.toList());

View File

@ -1,5 +1,6 @@
package com.nflg.mobilebroken.quotation.controller.app;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.common.pojo.ApiResult;

View File

@ -5,6 +5,9 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itextpdf.text.pdf.BaseFont;
import com.nflg.mobilebroken.common.constant.STATE;
import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.dto.ModelConfigEffectiveDTO;
@ -18,10 +21,7 @@ import com.nflg.mobilebroken.common.pojo.vo.ShoppingSearchVO;
import com.nflg.mobilebroken.common.pojo.vo.SimpleUserVO;
import com.nflg.mobilebroken.common.util.*;
import com.nflg.mobilebroken.quotation.controller.ControllerBase;
import com.nflg.mobilebroken.quotation.pojo.request.QuotationInfoUpdateRequest;
import com.nflg.mobilebroken.quotation.pojo.request.QuotationPriceUpdateRequest;
import com.nflg.mobilebroken.quotation.pojo.request.ShoppingInitRequest;
import com.nflg.mobilebroken.quotation.pojo.request.ShoppingSaveRequest;
import com.nflg.mobilebroken.quotation.pojo.request.*;
import com.nflg.mobilebroken.quotation.pojo.vo.QuotationOrderInfoVO;
import com.nflg.mobilebroken.quotation.pojo.vo.ShoppingCartPartVO;
import com.nflg.mobilebroken.quotation.pojo.vo.ShoppingCartVO;
@ -29,13 +29,29 @@ import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
@ -49,6 +65,8 @@ import java.util.stream.Collectors;
@RequestMapping("/app/shopping")
public class ShoppingController extends ControllerBase {
private static final PasswordEncoder PASSWORDENCODER = new BCryptPasswordEncoder();
@Resource
private IAppUserService appUserService;
@ -106,6 +124,9 @@ public class ShoppingController extends ControllerBase {
@Resource
private IQuotationModelForbidService forbidService;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 购物车-获取报价对象
*/
@ -122,10 +143,13 @@ public class ShoppingController extends ControllerBase {
.collect(Collectors.toList())
);
} else {
return ApiResult.success(List.of(new SimpleUserVO()
.setUserId(AppUserUtil.getUserId())
.setUserName(AppUserUtil.getUserName())
List<AppUser> appUsers = appUserService.getEndUsers();
return ApiResult.success(appUsers.stream()
.map(appUser -> new SimpleUserVO()
.setUserId(appUser.getId())
.setUserName(appUser.getName())
)
.collect(Collectors.toList())
);
}
}
@ -136,7 +160,7 @@ public class ShoppingController extends ControllerBase {
@PostMapping("/cart/init")
public ApiResult<ShoppingCartVO> init(@Valid @RequestBody ShoppingInitRequest request) {
Long categoryId = getCategoryId();
VUtils.trueThrowBusinessError(forbidService.isForbid(request.getModelId(), 1,request.getTargetId()))
VUtils.trueThrowBusinessError(forbidService.isForbid(request.getModelId(), 1, request.getTargetId()))
.throwMessage("该机型已禁售");
ModelPrice1VO modelPrice = priceService.getModelPrice(request.getModelId(), categoryId);
VUtils.trueThrowBusinessError(Objects.isNull(modelPrice)).throwMessage("该机型尚未设置价格");
@ -163,6 +187,7 @@ public class ShoppingController extends ControllerBase {
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());
if (Objects.nonNull(planModelItem)) {
@ -178,6 +203,7 @@ public class ShoppingController extends ControllerBase {
log.debug("机型【{}】标准配件系数为{},可选配件系数为{}", request.getModelNo(), standardRatio, optionalRatio);
}
}
}
vo.setActualFee(vo.getTotalFee().subtract(vo.getDiscount()).multiply(standardRatio));
log.debug("机型【{}】价格为{},优惠{}", request.getModelNo(), vo.getActualFee(), vo.getDiscount());
//获取部件配置
@ -312,6 +338,19 @@ public class ShoppingController extends ControllerBase {
return ApiResult.success();
}
/**
* 购物车-获取购物车数量
*/
@GetMapping("/cart/count")
public ApiResult<Long> getCount() {
return ApiResult.success(shoppingCartService.lambdaQuery()
.eq(QuotationShoppingCart::getStatus, 0)
.eq(QuotationShoppingCart::getCreateByType, AppUserUtil.isAgent() ? 1 : 0)
.eq(QuotationShoppingCart::getCreateById, AppUserUtil.getUserId())
.count()
);
}
/**
* 报价单-生成报价单
*/
@ -378,16 +417,19 @@ public class ShoppingController extends ControllerBase {
request.setPage(page);
request.setPageSize(pageSize);
IPage<QuotationSearchVO> datas = shoppingOrderService.search(request);
if(CollectionUtil.isEmpty(datas.getRecords())){
if (CollectionUtil.isEmpty(datas.getRecords())) {
return ApiResult.success(PageData.empty());
}
boolean isAgent = AppUserUtil.isAgent();
List<TBaseCustomer> customers;
List<AppUser> endUsers;
if (isAgent) {
customers = customerService.lambdaQuery()
.in(TBaseCustomer::getId, datas.getRecords().stream().map(QuotationSearchVO::getTargetId).collect(Collectors.toList()))
.list();
endUsers = null;
} else {
endUsers = appUserService.getEndUsers();
customers = null;
}
return ApiResult.success(datas, data -> {
@ -400,7 +442,12 @@ public class ShoppingController extends ControllerBase {
.orElse("")
);
} else {
data.setTargetName(AppUserUtil.getUserName());
data.setTargetName(endUsers.stream()
.filter(item -> item.getId().equals(data.getTargetId()))
.map(AppUser::getName)
.findFirst()
.orElse("")
);
}
//TODO 设置汇率价格
return data;
@ -487,6 +534,142 @@ public class ShoppingController extends ControllerBase {
return ApiResult.success();
}
// /**
// * 报价单-复制报价单
// */
// @PostMapping("/quotation/copy")
// 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();
// ShoppingCartVO vo = new ShoppingCartVO()
// .setModelId(request.getModelId())
// .setTargetId(request.getTargetId())
// .setCustomerName(request.getCustomerName())
// .setPriceId(modelPrice.getPriceId())
// .setConfigId(modelPrice.getConfigId())
// .setTotalFee(modelPrice.getAmount())
// .setActualFee(modelPrice.getAmount());
// log.debug("机型【{}】售价为{}", request.getModelNo(), modelPrice);
// 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());
// }
// }
// //系数
// 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());
// if (Objects.nonNull(planModelItem)) {
// log.debug("机型【{}】方案为{},系数:{}", request.getModelNo(), planModelItem.getName(), planModelItem.getRatio());
// standardRatio = NumberUtil.multiply(standardRatio, planModelItem.getRatio());
// log.debug("机型【{}】标准配件系数为{},可选配件系数为{}", request.getModelNo(), standardRatio, optionalRatio);
// vo.setPlanItemId(planModelItem.getId());
// } else {
// QuotationUserPlanDefault userPlanDefault = userPlanDefaultService.getEffectiveForUser(AppUserUtil.isAgent() ? 1 : 0, AppUserUtil.getUserId());
// if (Objects.nonNull(userPlanDefault)) {
// 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));
// log.debug("机型【{}】价格为{},优惠{}", request.getModelNo(), vo.getActualFee(), vo.getDiscount());
// //获取部件配置
// List<ModelConfigEffectiveDTO> parts = modelConfigService.getEffectives(modelPrice.getPriceId(), categoryId, MultilingualUtil.getLanguage());
// VUtils.trueThrowBusinessError(CollectionUtil.isEmpty(parts)).throwMessage("未获取到部件信息");
// vo.setStandardParts(parts.stream()
// .filter(part -> part.getParentId() == 0L && part.getType() == 1)
// .map(part -> {
// ShoppingCartPartVO vi = convert(part, optionalRatio);
// vi.setChildren(
// parts.stream()
// .filter(pi -> pi.getParentId().equals(part.getId()))
// .map(pi -> convert(pi, optionalRatio))
// .collect(Collectors.toList())
// );
// return vi;
// }
// ).collect(Collectors.toList())
// );
// vo.setOptionalParts(parts.stream()
// .filter(part -> part.getParentId() == 0L && part.getType() == 0)
// .map(part -> {
// ShoppingCartPartVO vi = convert(part, optionalRatio);
// vi.setChildren(
// parts.stream()
// .filter(pi -> pi.getParentId().equals(part.getId()))
// .map(pi -> convert(pi, optionalRatio))
// .collect(Collectors.toList())
// );
// return vi;
// }
// ).collect(Collectors.toList())
// );
// vo.setStandardFee(vo.getActualFee());
// return ApiResult.success(vo);
// carts.forEach(cart -> {
// QuotationShoppingOrderItem orderItem = orderItems.stream()
// .filter(item -> item.getCartId().equals(cart.getId()))
// .findFirst()
// .orElse(null);
// //获取部件配置
// List<ModelConfigEffectiveDTO> parts = shoppingCartItemService.getSelectedParts(cart.getId(), MultilingualUtil.getLanguage());
//
//
// cartVO.setStandardParts(parts.stream()
// .filter(part -> part.getParentId() == 0L && part.getType() == 1)
// .map(part -> {
// ShoppingCartPartVO vi = Convert.convert(ShoppingCartPartVO.class, part);
// vi.setAmount(
// vi.getAmount().add(
// parts.stream()
// .filter(pi -> pi.getParentId().equals(part.getId()) && pi.getType() == 0)
// .map(ModelConfigEffectiveDTO::getAmount)
// .reduce(BigDecimal::add)
// .orElse(BigDecimal.ZERO)
// )
// );
// return vi;
// }
// ).collect(Collectors.toList())
// );
// cartVO.setOptionalParts(parts.stream()
// .filter(part -> part.getParentId() == 0L && part.getType() == 0)
// .map(part -> {
// ShoppingCartPartVO vi = Convert.convert(ShoppingCartPartVO.class, part);
// vi.setAmount(
// vi.getAmount().add(
// parts.stream()
// .filter(pi -> pi.getParentId().equals(part.getId()) && pi.getType() == 0)
// .map(ModelConfigEffectiveDTO::getAmount)
// .reduce(BigDecimal::add)
// .orElse(BigDecimal.ZERO)
// )
// );
// return vi;
// }
// ).collect(Collectors.toList())
// );
// vo.getItems().add(cartVO);
// });
// return ApiResult.success(vo);
// }
/**
* 报价单-调价
*/
@ -522,25 +705,88 @@ public class ShoppingController extends ControllerBase {
return ApiResult.success();
}
/**
* 报价单-导出PDF
*/
@GetMapping("/quotation/exportToPdf")
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<>();
order.put("no", "DFENNIKFWE562D");
Map<String, Object> variables = new HashMap<>();
variables.put("info", order);
// 渲染HTML
TemplateEngine templateEngine = new TemplateEngine();
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("/templates/");
resolver.setSuffix(".html");
templateEngine.setTemplateResolver(resolver);
Context context = new Context();
context.setVariables(variables);
String html = templateEngine.process("pdf.html", context);
response.setContentType(MediaType.APPLICATION_PDF_VALUE);
String encode = URLEncoder.encode("aaaa" + ".pdf", StandardCharsets.UTF_8);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + encode);
// 生成PDF
try {
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.layout();
try (OutputStream outputStream = response.getOutputStream()) {
renderer.createPDF(outputStream);
}
} catch (Exception e) {
log.error("生成pdf出错", e);
throw new NflgException(STATE.BusinessError, "生成pdf出错");
}
}
/**
* 设置查看密码
*/
@PostMapping("/setPassword")
public ApiResult<Void> setPassword(@RequestBody String password) {
stringRedisTemplate.opsForHash().put("quotation:password:", String.valueOf(AppUserUtil.getUserId()), PASSWORDENCODER.encode(password));
return ApiResult.success();
}
/**
* 验证查看密码
*/
@PostMapping("/validatePassword")
public ApiResult<Boolean> validatePassword(@RequestBody String password) {
Object pwd = stringRedisTemplate.opsForHash().get("quotation:password:", String.valueOf(AppUserUtil.getUserId()));
VUtils.trueThrowBusinessError(Objects.isNull(pwd)).throwMessage("还未设置过查看密码");
if (!PASSWORDENCODER.matches(password, pwd.toString())) {
return ApiResult.error("查看密码不正确");
}
return ApiResult.success(true);
}
private Pair<BigDecimal, BigDecimal> getRatio(Long modelId) {
Pair<BigDecimal, BigDecimal> pair = Pair.of(BigDecimal.ONE, BigDecimal.ONE);
if (AppUserUtil.isAgent()) {
//代理商
if (AppUserUtil.isPrimary()) {
//主账号
QuotationModelRatioAgentItem pRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId,AppUserUtil.getUserId());
QuotationModelRatioAgentItem pRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId, AppUserUtil.getUserId());
if (Objects.nonNull(pRatioAgentItem)) {
pair = Pair.of(pRatioAgentItem.getStandardRatio(), pRatioAgentItem.getOptionalRatio());
}
} else {
//子账号
Integer parentUserId = appUserService.getPrimaryByChild(AppUserUtil.getUserId());
QuotationModelRatioAgentItem pRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId,parentUserId);
QuotationModelRatioAgentItem pRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId, parentUserId);
if (Objects.nonNull(pRatioAgentItem)) {
pair = Pair.of(NumberUtil.multiply(pair.getLeft(), pRatioAgentItem.getStandardRatio())
, NumberUtil.multiply(pair.getRight(), pRatioAgentItem.getOptionalRatio()));
}
QuotationModelRatioAgentItem cRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId,AppUserUtil.getUserId());
QuotationModelRatioAgentItem cRatioAgentItem = ratioAgentItemService.getEffectiveForUser(modelId, AppUserUtil.getUserId());
if (Objects.nonNull(cRatioAgentItem)) {
pair = Pair.of(NumberUtil.multiply(pair.getLeft(), cRatioAgentItem.getStandardRatio())
, NumberUtil.multiply(pair.getRight(), cRatioAgentItem.getOptionalRatio()));

View File

@ -0,0 +1,28 @@
package com.nflg.mobilebroken.quotation.pojo.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class QuotationCopyRequest {
/**
* 报价ID
*/
@NotNull
private Long id;
/**
* 客户名称
*/
@NotBlank
private String customerName;
/**
* 报价对象ID
*/
@NotNull
private Integer targetId;
}

View File

@ -23,7 +23,6 @@ public class ShoppingInitRequest {
/**
* 客户名称
*/
@NotBlank
private String customerName;
/**
@ -31,4 +30,9 @@ public class ShoppingInitRequest {
*/
@NotNull
private Integer targetId;
/**
* 是否显示底价
*/
private Boolean showLowestPrice = false;
}

View File

@ -7,9 +7,23 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class DiscountApplyForVO {
private Integer id;
/**
* 用户ID
*/
private Integer sourceId;
private String agencyCompanyName;
/**
* 用户类型 0 终端用户1 代理商公司
*/
private Integer sourceType;
/**
* 名称
*/
private String name;
/**
* 区域名称
*/
private String areaName;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title th:text="${info.no}"></title>
<style>
@page {
size: A4;
margin: 2cm;
@top-left {
content: element(header-logo);
vertical-align: center;
}
@top-right {
content: "测试数据";
font-family: SimSun, serif;
font-size: 10pt;
}
@bottom-center {
content: counter(page) " / " counter(pages);
font-family: SimSun, serif;
font-size: 10pt;
}
}
body {
font-family: SimSun, serif;
font-size: 12pt;
margin: 1cm;
line-height: 1.2;
}
.header {
position: running(header-logo);
}
.data1:after {
content: "";
display: block;
clear: both;
}
.data1 {
width: 100%;
}
</style>
</head>
<body>
<div class="header">
<img alt="" src="../images/logo.png" style="width: 200px; "/>
</div>
<div>
<div class="data1">
<div style="width: 50%;float: left;">
<div style="font-weight: bold">CLOSE TO OUR CUSTOMERS</div>
<div th:text="${info.no}">报价主体</div>
</div>
<div style="width: 50%;float: right">
<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>
</div>
</div>
<div style="text-align: right">
<span>报价有效期</span>
<span th:text="${info.no}">报价有效期</span>
</div>
<hr/>
<div style="font-weight: bold;margin-top: 20px;">
<span>报价单号</span>
<span th:text="${info.no}">报价单号</span>
</div>
<div style="font-weight: bold;margin-top: 20px;">
<span>总价</span>
<span th:text="${info.no}">总价</span>
</div>
<div style="margin-top: 20px;">
<div>包含下列费用</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>
<div th:text="${info.no}">- 随机备件价格</div>
<div th:text="${info.no}">- 运费</div>
</div>
<div style="margin-top: 20px;">
<span>付款方式</span>
<span th:text="${info.no}">付款方式</span>
</div>
<div style="margin-top: 20px;">
<div style="font-size: 20pt;" th:text="${info.no}">机型</div>
<img style="width: 100%;" src="../images/logo.png"/>
<div th:text="${info.no}">设备简介</div>
</div>
<div style="margin-top: 20px;">
<div style="font-size: 20pt;" th:text="${info.no}">配置详情</div>
<div th:text="${info.no}">标准配置</div>
<div th:text="${info.no}"><input type="checkbox">AAA</div>
<div>
<div th:text="${info.no}">&nbsp;<input type="checkbox">AAA</div>
</div>
</div>
<div th:text="${info.no}">可选配置</div>
<div th:text="${info.no}"><input type="checkbox">AAA</div>
<div>
<div th:text="${info.no}">&nbsp;<input type="checkbox">AAA</div>
</div>
<div th:text="${info.no}">其他配置</div>
<div th:text="${info.no}">油漆要求</div>
</div>
</body>
</html>

View File

@ -37,7 +37,7 @@ public class QuotationModelDiscountApply implements Serializable {
private Long discountId;
/**
* 用户类型 0 内部用户1 代理商公司
* 用户类型 0 终端用户1 代理商公司
*/
private Integer sourceType;

View File

@ -85,4 +85,6 @@ public interface IAppUserService extends IService<AppUser> {
DictionaryItem getCategory(AppUser cuser);
List<TBaseCustomer> getCustomers(Integer userId);
List<AppUser> getEndUsers();
}

View File

@ -34,4 +34,6 @@ public interface ITBaseCustomerService extends IService<TBaseCustomer> {
Collection<String> getAreas(List<Integer> companyIds);
Collection<Integer> getAreaIds(List<Integer> companyIds);
List<TBaseCustomer> getForQuotation();
}

View File

@ -528,6 +528,14 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
.list();
}
@Override
public List<AppUser> getEndUsers() {
return lambdaQuery()
.eq(AppUser::getIsDel,0)
.eq(AppUser::getType,1)
.list();
}
private void bindChildren1(AreaSimpleVO vo) {
List<AppArea> datas = appAreaService.lambdaQuery().eq(AppArea::getParentId, vo.getId()).eq(AppArea::getEnable, true).list();
List<AreaSimpleVO> vos = datas.stream().map(d -> new AreaSimpleVO().setId(d.getId()).setName(d.getName())).collect(Collectors.toList());

View File

@ -140,4 +140,13 @@ public class TBaseCustomerServiceImpl extends ServiceImpl<TBaseCustomerMapper, T
.map(TBaseArea::getId)
.collect(Collectors.toSet());
}
@Override
public List<TBaseCustomer> getForQuotation() {
return lambdaQuery()
.eq(TBaseCustomer::getDelIs, 0)
.eq(TBaseCustomer::getEnableState, 1)
.eq(TBaseCustomer::getQuotationUse, true)
.list();
}
}

View File

@ -115,7 +115,7 @@
</select>
<select id="searchForQuotation1" resultType="com.nflg.mobilebroken.common.pojo.vo.QuotationProductModelSearchVO">
SELECT pm.*,dit.`value` as 'moduleName',psi.`name` as 'seriesName',pti.`name` as 'typeName'
SELECT pm.*,dit.`value` as 'moduleName',psi.`name` as 'seriesName',pti.`name` as 'typeName',pm.image
,IF(dis.model_id is null,0,1) AS 'hasDiscount'
FROM product_model pm
LEFT JOIN (SELECT model_id FROM quotation_model_discount WHERE discount_status=1) dis ON dis.model_id=pm.batch_number
@ -144,14 +144,15 @@
</select>
<select id="getInfoForQuotation" resultType="com.nflg.mobilebroken.common.pojo.vo.QuotationProductModelInfoVO">
SELECT dit.`value` as 'moduleName',psi.`name` as 'seriesName',pti.`name` as 'typeName',pm.`no`,pm.image,pmii.`desc`,pmii.feature
SELECT pm.batch_number,dit.`value` as 'moduleName',psi.`name` as 'seriesName',pti.`name` as 'typeName',pm.`no`
,pm.image,pmii.`desc`,pmii.feature
FROM product_model pm
LEFT JOIN dictionary_item_translate dit ON dit.dictionary_item_id=pm.module_id AND dit.language_code=#{language}
INNER JOIN product_series ps ON pm.series_number=ps.batch_number AND ps.`enable`=1 AND ps.state=1
LEFT JOIN product_series_info psi ON psi.series_id=ps.id AND psi.language_code=#{language}
INNER JOIN product_type pt ON pm.type_number=pt.batch_number AND pt.`enable`=1 AND pt.state=1
LEFT JOIN product_type_info pti ON pti.type_id=pt.id AND pti.language_code=#{language}
LEFT JOIN product_model_intro pmi ON pmi.model_id=pm.id
LEFT JOIN product_model_intro pmi ON pmi.model_id=pm.id and pmi.state=1
LEFT JOIN product_model_intro_item pmii ON pmii.model_intro_id=pmi.id AND pmii.language_code=#{language}
WHERE pm.id=#{id}
</select>

View File

@ -84,7 +84,7 @@
</select>
<select id="getSimpleListByLanguage" resultType="com.nflg.mobilebroken.common.pojo.vo.ProductTypeSimpleVO">
select pt.batch_number, pti.name
select pt.batch_number as 'typeNumber', pti.name
from product_type pt
LEFT JOIN product_type_info pti ON pti.type_id = pt.id
where pt.state = 1
@ -103,7 +103,8 @@
</select>
<select id="searchSimpleList" resultType="com.nflg.mobilebroken.common.pojo.vo.ProductTypeSimpleVO">
SELECT di.name as 'moduleName',psi.name as 'seriesName',pti.name,pti.image,pt.batch_number
SELECT di.name as 'moduleName',ps.batch_number as 'seriesNumber',psi.name as 'seriesName',pti.name,pti.image
,pt.batch_number as 'typeNumber'
FROM product_type pt
INNER JOIN product_type_info pti ON pt.id = pti.type_id AND pti.language_code = #{language}
left join product_series ps on pt.series_number = ps.batch_number and ps.state =1 and ps.enable=1

View File

@ -17,7 +17,7 @@
</select>
<select id="getAgentUsers" resultType="com.nflg.mobilebroken.common.pojo.vo.SimpleUserVO">
SELECT au.id as 'userId',au.`name` as 'userName'
SELECT distinct au.id as 'userId',au.`name` as 'userName'
FROM quotation_model_ratio_agent qmra
INNER JOIN quotation_model_ratio_agent_item qmrai ON qmra.id=qmrai.ratio_id
INNER JOIN app_user au ON qmrai.user_id=au.id

View File

@ -11,7 +11,8 @@
<select id="search" resultType="com.nflg.mobilebroken.common.pojo.vo.PlanSearchItemVO">
SELECT qupmi.id,qupmi.plan_id,pm.batch_number as 'modelId',pm.`no` as 'modelNo',qupmi.`name`,qupmi.ratio,qupmi.is_default
FROM product_model pm
FROM v_quotation_model_price vqmp
inner join product_model pm on vqmp.model_id=pm.batch_number
LEFT JOIN quotation_user_plan_model_item qupmi ON qupmi.model_id=pm.batch_number
left join quotation_user_plan_model qupm on qupmi.plan_id=qupm.id and qupm.`status`=1 and qupm.create_by_type=#{userType} and qupm.create_by_id=#{userId}
WHERE pm.state=1