Compare commits

..

2 Commits

Author SHA1 Message Date
曹鹏飞 dc4e3f04f1 feat(plan): 优化默认系数设置与区域类别处理
- DefaultRatioVO 中添加@NotNull校验,确保比例和区域不能为空
- getDefaultRatio接口支持多区域报价用户,返回对应所有分类的默认比例
- setDefaultRatio接口改为接收多个默认系数列表,批量保存并删除旧数据
- 优化分页查询逻辑,支持多区域时按分类获取销售价格和区域名称
- 将单个类别ID接口改为返回完整DictionaryItem对象,包含ID和名称
- PlanSearchItemVO增加areaName字段,用于展示区域名称信息
2026-05-15 17:19:15 +08:00
曹鹏飞 b9db108346 feat(migration): 迁移产品中心机型参数数据到新表结构
- 新增ProductModelParamsData和ProductModelParamsDataLanguage实体及相关Mapper和Service
- 在TestController中添加migrationProductModelParamsItems接口以实现数据迁移
- 迁移逻辑根据语言和批次编码分组转换旧表数据到新表数据结构
- 支持多语言参数数据的生成和批量保存
- 使用Spring事务保证迁移操作原子性
- 引入Hutool转换工具及雪花算法生成唯一ID
2026-05-15 17:19:05 +08:00
12 changed files with 500 additions and 46 deletions

View File

@ -47,6 +47,11 @@ public class PlanSearchItemVO {
@NotNull(message = "区域不能为空") @NotNull(message = "区域不能为空")
private Long areaId; private Long areaId;
/**
* 区域名称
*/
private String areaName;
/** /**
* 是否默认 * 是否默认
*/ */

View File

@ -1,6 +1,7 @@
package com.nflg.mobilebroken.quotation.controller; package com.nflg.mobilebroken.quotation.controller;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.common.pojo.ApiResult; import com.nflg.mobilebroken.common.pojo.ApiResult;
@ -8,6 +9,7 @@ import com.nflg.mobilebroken.common.util.VUtils;
import com.nflg.mobilebroken.repository.entity.*; import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*; import com.nflg.mobilebroken.repository.service.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -48,6 +50,18 @@ public class TestController extends ControllerBase {
@Resource @Resource
private ITBaseCustomerService customerService; private ITBaseCustomerService customerService;
@Resource
private IProductModelParamsItemService productModelParamsItemService;
@Resource
private IProductModelParamsDataService productModelParamsDataService;
@Resource
private IProductModelParamsDataLanguageService productModelParamsDataLanguageService;
@Resource
private ILanguageService languageService;
/** /**
* 初始化机型表的BatchNumber字段数据 * 初始化机型表的BatchNumber字段数据
*/ */
@ -183,4 +197,102 @@ public class TestController extends ControllerBase {
} }
return ApiResult.success(); return ApiResult.success();
} }
/**
* 将产品中心机型参数迁移到新表中
*/
@Transactional
@GetMapping("migrationProductModelParamsItems")
public ApiResult<Void> migrationProductModelParamsItems() {
List<Language> languages = languageService.list();
List<ProductModelParamsItem> items = productModelParamsItemService.list();
List<ProductModelParamsData> datas = new ArrayList<>();
List<ProductModelParamsDataLanguage> dataLanguages = new ArrayList<>();
Map<String, List<ProductModelParamsItem>> itemMap = items.stream().collect(Collectors.groupingBy(ProductModelParamsItem::getBatchNumber));
itemMap.forEach((batchNumber, its) -> {
ProductModelParamsItem cnItem = its.stream()
.filter(it -> StrUtil.equals(it.getLanguageCode(), "cn"))
.findFirst()
.get();
ProductModelParamsData pData = datas.stream()
.filter(d -> Objects.equals(d.getParamsId(), Long.valueOf(cnItem.getModelParamsId()))
&& Objects.equals(d.getParentId(), 0L) && StrUtil.equals(d.getName(), cnItem.getIndexName())
)
.findFirst()
.orElse(null);
if (Objects.isNull(pData)) {
pData = Convert.convert(ProductModelParamsData.class, cnItem);
pData.setId(IdUtil.getSnowflakeNextId());
pData.setParamsId(Long.valueOf(cnItem.getModelParamsId()));
pData.setParentId(0L);
pData.setName(cnItem.getIndexName());
pData.setType(1);
pData.setValue(null);
datas.add(pData);
}
ProductModelParamsData finalPData = pData;
ProductModelParamsData cData=datas.stream()
.filter(d -> Objects.equals(d.getParamsId(), finalPData.getParamsId())
&& Objects.equals(d.getParentId(), finalPData.getId())
&& StrUtil.equals(d.getName(), cnItem.getName())
)
.findFirst()
.orElse(null);
if (Objects.isNull(cData)){
cData = Convert.convert(ProductModelParamsData.class, cnItem);
cData.setId(IdUtil.getSnowflakeNextId());
cData.setParamsId(pData.getParamsId());
cData.setParentId(pData.getId());
cData.setType(1);
cData.setValue(null);
datas.add(cData);
}
ProductModelParamsData finalCData = cData;
its.stream()
.sorted(Comparator.comparing(ProductModelParamsItem::getId))
.forEach(it -> {
Language language = languages.stream()
.filter(l -> StrUtil.equals(l.getCode(), it.getLanguageCode()))
.findFirst()
.get();
ProductModelParamsDataLanguage pDataLanguage = dataLanguages.stream()
.filter(d -> Objects.equals(d.getParamsId(), finalPData.getParamsId())
&& Objects.equals(d.getParamsDataId(), finalPData.getId())
&& Objects.equals(d.getName(), it.getIndexName())
&& Objects.equals(d.getLanguageId(), language.getId()
))
.findFirst()
.orElse(null);
if (Objects.isNull(pDataLanguage)){
pDataLanguage = Convert.convert(ProductModelParamsDataLanguage.class, it);
pDataLanguage.setId(IdUtil.getSnowflakeNextId());
pDataLanguage.setParamsId(finalPData.getParamsId());
pDataLanguage.setParamsDataId(finalPData.getId());
pDataLanguage.setName(it.getIndexName());
pDataLanguage.setLanguageId(language.getId());
pDataLanguage.setValue(null);
dataLanguages.add(pDataLanguage);
}
ProductModelParamsDataLanguage cDataLanguage = dataLanguages.stream()
.filter(d -> Objects.equals(d.getParamsId(), finalCData.getParamsId())
&& Objects.equals(d.getParamsDataId(), finalCData.getId())
&& Objects.equals(d.getName(), it.getName())
&& Objects.equals(d.getLanguageId(), language.getId()
))
.findFirst()
.orElse(null);
if (Objects.isNull(cDataLanguage)) {
cDataLanguage = Convert.convert(ProductModelParamsDataLanguage.class, it);
cDataLanguage.setId(IdUtil.getSnowflakeNextId());
cDataLanguage.setParamsId(finalCData.getParamsId());
cDataLanguage.setParamsDataId(finalCData.getId());
cDataLanguage.setLanguageId(language.getId());
dataLanguages.add(cDataLanguage);
}
});
});
productModelParamsDataService.saveBatch(datas);
productModelParamsDataLanguageService.saveBatch(dataLanguages);
return ApiResult.success();
}
} }

View File

@ -21,7 +21,6 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@ -72,45 +71,71 @@ public class PlanController extends ControllerBase {
*/ */
@GetMapping("/getDefaultRatio") @GetMapping("/getDefaultRatio")
public ApiResult<List<DefaultRatioVO>> getDefaultRatio() { public ApiResult<List<DefaultRatioVO>> getDefaultRatio() {
return ApiResult.success( List<QuotationUserPlanDefault> datas = planDefaultService.lambdaQuery()
planDefaultService.lambdaQuery()
.eq(QuotationUserPlanDefault::getCreateByType, AppUserUtil.isAgent() ? 1 : 0) .eq(QuotationUserPlanDefault::getCreateByType, AppUserUtil.isAgent() ? 1 : 0)
.eq(QuotationUserPlanDefault::getCreateById, AppUserUtil.getUserId()) .eq(QuotationUserPlanDefault::getCreateById, AppUserUtil.getUserId())
.list() .list();
if (isMultiRegionQuotationsUser()) {
List<Long> categoryIds = dictionaryItemService.getListByDictionaryCode("DirectSalesCategory")
.stream() .stream()
.map(pd -> new DefaultRatioVO() .map(DictionaryItem::getId)
.setAreaId(pd.getAreaId()) .collect(Collectors.toList());
.setRatio(pd.getRatio()) return ApiResult.success(
) categoryIds.stream()
.map(categoryId -> {
QuotationUserPlanDefault d = datas.stream()
.filter(pd -> Objects.equals(pd.getAreaId(), categoryId))
.findFirst()
.orElse(null);
if (Objects.isNull(d)) {
return new DefaultRatioVO()
.setAreaId(categoryId);
} else {
return new DefaultRatioVO()
.setRatio(d.getRatio());
}
})
.collect(Collectors.toList()) .collect(Collectors.toList())
); );
} else {
DictionaryItem category = getCategory();
QuotationUserPlanDefault d = datas.stream()
.filter(pd -> Objects.equals(pd.getAreaId(), category.getId()))
.findFirst()
.orElse(null);
if (Objects.isNull(d)) {
return ApiResult.success(
List.of(new DefaultRatioVO().setAreaId(category.getId()))
);
} else {
return ApiResult.success(
List.of(new DefaultRatioVO().setRatio(d.getRatio()))
);
}
}
} }
/** /**
* 设置方案默认系数 * 设置方案默认系数
*/ */
@Transactional
@PostMapping("/setDefaultRatio") @PostMapping("/setDefaultRatio")
public ApiResult<Void> setDefaultRatio(@RequestParam @Min(1) BigDecimal ratio) { public ApiResult<Void> setDefaultRatio(@Valid @RequestBody @NotEmpty List<DefaultRatioVO> request) {
QuotationUserPlanDefault planDefault = planDefaultService.lambdaQuery() planDefaultService.lambdaUpdate()
.eq(QuotationUserPlanDefault::getCreateByType, AppUserUtil.isAgent() ? 1 : 0) .eq(QuotationUserPlanDefault::getCreateByType, AppUserUtil.isAgent() ? 1 : 0)
.eq(QuotationUserPlanDefault::getCreateById, AppUserUtil.getUserId()) .eq(QuotationUserPlanDefault::getCreateById, AppUserUtil.getUserId())
.one(); .remove();
if (Objects.isNull(planDefault)) { planDefaultService.saveBatch(
planDefaultService.save( request.stream()
new QuotationUserPlanDefault() .map(r -> new QuotationUserPlanDefault()
.setRatio(ratio) .setRatio(r.getRatio())
.setAreaId(r.getAreaId())
.setCreateByType(AppUserUtil.isAgent() ? 1 : 0) .setCreateByType(AppUserUtil.isAgent() ? 1 : 0)
.setCreateById(AppUserUtil.getUserId()) .setCreateById(AppUserUtil.getUserId())
.setCreateBy(AppUserUtil.getUserName()) .setCreateBy(AppUserUtil.getUserName())
.setCreateTime(LocalDateTime.now()) .setCreateTime(LocalDateTime.now())
).collect(Collectors.toList())
); );
} else {
planDefaultService.lambdaUpdate()
.set(QuotationUserPlanDefault::getRatio, ratio)
.set(QuotationUserPlanDefault::getUpdateTime, LocalDateTime.now())
.eq(QuotationUserPlanDefault::getId, planDefault.getId())
.update();
}
return ApiResult.success(); return ApiResult.success();
} }
@ -135,25 +160,22 @@ public class PlanController extends ControllerBase {
pageData.setItems(datas); pageData.setItems(datas);
vo.setDatas(pageData); vo.setDatas(pageData);
boolean multiRegionQuotations = isMultiRegionQuotationsUser(); boolean multiRegionQuotations = isMultiRegionQuotationsUser();
List<Long> categoryIds = new ArrayList<>(); List<DictionaryItem> categories = new ArrayList<>();
if (multiRegionQuotations) { if (multiRegionQuotations) {
categoryIds = dictionaryItemService.getListByDictionaryCode("DirectSalesCategory") categories = dictionaryItemService.getListByDictionaryCode("DirectSalesCategory");
.stream()
.map(DictionaryItem::getId)
.collect(Collectors.toList());
} else { } else {
categoryIds.add(getCategoryId()); categories.add(getCategory());
} }
List<ModelPriceVO> prices = priceService.getAllModelPrice(); List<ModelPriceVO> prices = priceService.getAllModelPrice();
Map<Long, List<PlanSearchItemVO>> fgroup = items.stream().collect(Collectors.groupingBy(PlanSearchItemVO::getModelId)); Map<Long, List<PlanSearchItemVO>> fgroup = items.stream().collect(Collectors.groupingBy(PlanSearchItemVO::getModelId));
pageData.setTotal(fgroup.size()); pageData.setTotal(fgroup.size());
for (Map.Entry<Long, List<PlanSearchItemVO>> entry : fgroup.entrySet()) { for (Map.Entry<Long, List<PlanSearchItemVO>> entry : fgroup.entrySet()) {
if (index >= startIndex && index < endIndex) { if (index >= startIndex && index < endIndex) {
categoryIds.forEach(categoryId -> { categories.forEach(category -> {
BigDecimal salePrice = getSalePrice(entry.getKey(), categoryId, prices); BigDecimal salePrice = getSalePrice(entry.getKey(), category.getId(), prices);
List<PlanSearchItemVO> vos = entry.getValue() List<PlanSearchItemVO> vos = entry.getValue()
.stream() .stream()
.filter(pv -> Objects.equals(pv.getAreaId(), categoryId)) .filter(pv -> Objects.equals(pv.getAreaId(), category.getId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (CollectionUtil.isEmpty(vos)) { if (CollectionUtil.isEmpty(vos)) {
entry.getValue().forEach(item -> { entry.getValue().forEach(item -> {
@ -164,7 +186,8 @@ public class PlanController extends ControllerBase {
.setModelNo(item.getModelNo()) .setModelNo(item.getModelNo())
.setName(item.getName()) .setName(item.getName())
.setRatio(item.getRatio()) .setRatio(item.getRatio())
.setAreaId(categoryId) .setAreaId(category.getId())
.setAreaName(category.getName())
.setStandardPrice(salePrice) .setStandardPrice(salePrice)
); );
}); });
@ -178,7 +201,8 @@ public class PlanController extends ControllerBase {
.setModelNo(item.getModelNo()) .setModelNo(item.getModelNo())
.setName(item.getName()) .setName(item.getName())
.setRatio(item.getRatio()) .setRatio(item.getRatio())
.setAreaId(categoryId) .setAreaId(category.getId())
.setAreaName(category.getName())
.setStandardPrice(salePrice) .setStandardPrice(salePrice)
); );
}); });
@ -189,15 +213,15 @@ public class PlanController extends ControllerBase {
return ApiResult.success(vo); return ApiResult.success(vo);
} }
private Long getCategoryId() { private DictionaryItem getCategory() {
if (AppUserUtil.isAgent()) { if (AppUserUtil.isAgent()) {
DictionaryItem di = appUserService.getCategory(appUserService.getById(AppUserUtil.getUserId())); DictionaryItem di = appUserService.getCategory(appUserService.getById(AppUserUtil.getUserId()));
VUtils.trueThrowBusinessError(Objects.isNull(di)).throwMessage("请联系管理员设置区域类别"); VUtils.trueThrowBusinessError(Objects.isNull(di)).throwMessage("请联系管理员设置区域类别");
return di.getId(); return di;
} else { } else {
Long categoryId = adminUserService.getById(AppUserUtil.getUserId()).getCategoryId(); Long categoryId = adminUserService.getById(AppUserUtil.getUserId()).getCategoryId();
VUtils.trueThrowBusinessError(Objects.isNull(categoryId)).throwMessage("请联系管理员设置区域类别"); VUtils.trueThrowBusinessError(Objects.isNull(categoryId)).throwMessage("请联系管理员设置区域类别");
return categoryId; return dictionaryItemService.getById(categoryId);
} }
} }

View File

@ -3,6 +3,7 @@ package com.nflg.mobilebroken.quotation.pojo.vo;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
@Data @Data
@ -12,10 +13,12 @@ public class DefaultRatioVO {
/** /**
* 比例 * 比例
*/ */
@NotNull(message = "比例不能为空")
private BigDecimal ratio; private BigDecimal ratio;
/** /**
* 区域国内/国外字典id * 区域国内/国外字典id
*/ */
@NotNull(message = "区域不能为空")
private Long areaId; private Long areaId;
} }

View File

@ -0,0 +1,127 @@
package com.nflg.mobilebroken.repository.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 产品中心-机型-参数数据
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("product_model_params_data")
public class ProductModelParamsData implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 机型参数id
*/
private Long paramsId;
/**
* 上级id
*/
private Long parentId;
/**
* 名称
*/
private String name;
/**
* 参数值
*/
private String value;
/**
* 是否主要参数
*/
private Boolean main;
/**
* 主要参数排序号
*/
private Integer mainSort;
/**
* 是否重要参数
*/
private Boolean important;
/**
* 重要参数排序号
*/
private Integer importantSort;
/**
* 是否参与比较
*/
private Boolean compare;
/**
* 图标
*/
private String ico;
/**
* 描述
*/
private String remark;
/**
* 类别0可选配置1标准配置
*/
private Integer type;
/**
* 选配类别0新增可选1替换可选
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Integer optionalType;
/**
* 图片
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String imageUrl;
/**
* 分组名称同组的可选配置替换
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String groupName;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,79 @@
package com.nflg.mobilebroken.repository.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 产品中心-机型-参数数据-多语言
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("product_model_params_data_language")
public class ProductModelParamsDataLanguage implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 机型参数id
*/
private Long paramsId;
/**
* 机型参数数据id
*/
private Long paramsDataId;
/**
* 语言id
*/
private Long languageId;
/**
* 名称
*/
private String name;
/**
* 参数值
*/
private String value;
/**
* 描述
*/
private String remark;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,16 @@
package com.nflg.mobilebroken.repository.mapper;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsDataLanguage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 产品中心-机型-参数数据-多语言 Mapper 接口
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
public interface ProductModelParamsDataLanguageMapper extends BaseMapper<ProductModelParamsDataLanguage> {
}

View File

@ -0,0 +1,16 @@
package com.nflg.mobilebroken.repository.mapper;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 产品中心-机型-参数数据 Mapper 接口
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
public interface ProductModelParamsDataMapper extends BaseMapper<ProductModelParamsData> {
}

View File

@ -0,0 +1,16 @@
package com.nflg.mobilebroken.repository.service;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsDataLanguage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 产品中心-机型-参数数据-多语言 服务类
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
public interface IProductModelParamsDataLanguageService extends IService<ProductModelParamsDataLanguage> {
}

View File

@ -0,0 +1,16 @@
package com.nflg.mobilebroken.repository.service;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsData;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 产品中心-机型-参数数据 服务类
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
public interface IProductModelParamsDataService extends IService<ProductModelParamsData> {
}

View File

@ -0,0 +1,20 @@
package com.nflg.mobilebroken.repository.service.impl;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsDataLanguage;
import com.nflg.mobilebroken.repository.mapper.ProductModelParamsDataLanguageMapper;
import com.nflg.mobilebroken.repository.service.IProductModelParamsDataLanguageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 产品中心-机型-参数数据-多语言 服务实现类
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
@Service
public class ProductModelParamsDataLanguageServiceImpl extends ServiceImpl<ProductModelParamsDataLanguageMapper, ProductModelParamsDataLanguage> implements IProductModelParamsDataLanguageService {
}

View File

@ -0,0 +1,20 @@
package com.nflg.mobilebroken.repository.service.impl;
import com.nflg.mobilebroken.repository.entity.ProductModelParamsData;
import com.nflg.mobilebroken.repository.mapper.ProductModelParamsDataMapper;
import com.nflg.mobilebroken.repository.service.IProductModelParamsDataService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 产品中心-机型-参数数据 服务实现类
* </p>
*
* @author 代码生成器生成
* @since 2026
*/
@Service
public class ProductModelParamsDataServiceImpl extends ServiceImpl<ProductModelParamsDataMapper, ProductModelParamsData> implements IProductModelParamsDataService {
}