Commit c6bf40e2 by Lizh

对照ts的实现,迁移erpPage接口

parent ee57572a
......@@ -9,7 +9,9 @@ import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
......@@ -20,7 +22,7 @@ import java.util.List;
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CustomProductInfoSnakeDTO extends PageRequest {
public class CustomProductInfoQuerySnakeDTO extends PageRequest {
@Serial
private static final long serialVersionUID = 1L;
......@@ -129,11 +131,42 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
/**
* 状态:1待上架 10已上架 20已下架 30待下架 40已作废
* <p>
* 支持单值(Number)和逗号分隔多值(String "1,10,20")。
* 多值时通过 statusList 字段存储,status 为 null。
*/
@Schema(description = "状态:1待上架 10已上架 20已下架 30待下架 40已作废")
@Schema(description = "状态:1待上架 10已上架 20已下架 30待下架 40已作废,支持逗号分隔多值")
private Integer status;
/**
* 状态多值列表(ERP 专用),从 "status" JSON 字段的逗号分隔字符串解析。
*/
@Schema(hidden = true)
private List<Integer> statusList;
/** 兼容逗号分隔多值与单值 */
@JsonSetter("status")
public void setStatusValue(Object value) {
if (value instanceof String s && s.contains(",")) {
try {
this.statusList = Arrays.stream(s.split(","))
.map(String::trim).filter(v -> !v.isEmpty())
.map(Integer::parseInt).collect(Collectors.toList());
} catch (NumberFormatException ignored) {
this.statusList = null;
}
} else if (value instanceof Number n) {
this.status = n.intValue();
} else if (value instanceof String s) {
try {
this.status = Integer.parseInt(s.trim());
} catch (NumberFormatException ignored) {
this.status = null;
}
}
}
/**
* 是否九猫处理(0=否 1=是 2=不确定/未设置)
* <p>
* 兼容前端发送 Boolean(true→1, false→0)和 Number(0/1/2)。
......@@ -231,11 +264,33 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
private BigDecimal sales_price_max;
/**
* 排序
* 排序值(用于过滤),与 ERP 排序方向(asc/desc)共享 "sort" JSON 字段。
* <p>
* 前端传 Number → 存入此字段;传 String "asc"/"desc" → 存入 sortDirection。
*/
@Schema(description = "排序")
@Schema(description = "排序值或排序方向")
private Integer sort;
/** ERP 排序方向(asc/desc),从 "sort" JSON 字段解析 */
@Schema(hidden = true)
private String sortDirection;
/** 兼容 sort 字段的 Number(排序值)和 String "asc"/"desc"(排序方向) */
@JsonSetter("sort")
public void setSortValue(Object value) {
if (value instanceof String s && ("asc".equalsIgnoreCase(s) || "desc".equalsIgnoreCase(s))) {
this.sortDirection = s.toLowerCase();
} else if (value instanceof Number n) {
this.sort = n.intValue();
} else if (value instanceof String s) {
try {
this.sort = Integer.parseInt(s.trim());
} catch (NumberFormatException ignored) {
this.sort = null;
}
}
}
/**
*
*/
......
package com.jomalls.custom.app.dto;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* Entity
*
* @author huanying
* @date 2026-06-02 19:07:12
*/
@Data
public class CustomProductInfoSaveSnakeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* SKU 筛选
*/
@Schema(description = "SKU 筛选")
@Size(max = 20, message = "sku长度不能超过20个字符")
private String sku;
/**
* 商品名称
*/
@Schema(description = "name", example = "")
@NotNull(message= "商品名称不能为空")
@Size(max = 255, message = "商品名称长度不能超过255个字符")
private String name;
/**
* 商品类别ID
*/
@Schema(description = "商品类别ID", example = "")
@NotNull(message = "商品类别不能为空")
private Integer category_id;
/**
* 材质
*/
@Schema(description = "材质", example = "")
@NotNull(message= "材质不能为空")
private String material;
/**
* 印花类型:0满印 1局部印 2代发普货
*/
@Schema(description = "印花类型:0满印 1局部印 2代发普货")
@Min(value = 0, message = "请选择正确的印花类型")
@Max(value = 2, message = "请选择正确的印花类型")
private Integer print_type;
/**
* 货号
*/
@Schema(description = "货号")
@Size(max = 20, message = "货号长度不能超过20个字符")
private String product_no;
/**
* 产地编码
*/
@Schema(description = "产地编码")
@NotBlank(message = "产地不能为空")
private String origin_code;
/**
* 产地中文名
*/
@Schema(description = "产地中文名")
@NotBlank(message = "产地不能为空")
private String origin_name_cn;
/**
* 产地英文名
*/
@Schema(description = "产地英文名")
@NotBlank(message = "产地不能为空")
private String origin_name_en;
/**
* 币种编码
*/
@Schema(description = "币种编码")
@NotBlank(message = "币种不能为空")
private String currency_code;
/**
* 币种名称
*/
@Schema(description = "币种名称")
@NotBlank(message = "币种不能为空")
private String currency_name;
/**
* 产品类型:platform/customer
*/
@Schema(description = "产品类型:platform/customer")
@NotBlank(message = "产品类型不能为空")
private String product_type;
/**
* 仓库 ID 列表
*/
@Schema(description = "仓库 ID 列表")
private List<Integer> warehouseIds;
/**
* 商品title
*/
@Schema(description = "title")
@Size(max = 255, message = "title长度不能超过255个字符")
private String title;
/**
* 工艺 ID 列表
*/
@Schema(description = "工艺 ID 列表")
private List<Integer> craftIds;
/**
* 状态:1待上架 10已上架 20已下架 30待下架 40已作废
*/
@Schema(description = "状态:1待上架 10已上架 20已下架 30待下架 40已作废")
private Integer status;
/**
* 是否九猫处理(0=否 1=是)
* <p>
* 兼容前端发送 Boolean(true→1, false→0)。
*/
@Schema(description = "是否九猫处理(false=否 true=是)")
private Boolean processing;
/**
* 英文备注
*/
@Schema(description = "英文备注")
private String remark;
/**
* 中文备注
*/
@Schema(description = "中文备注")
private String cnRemark;
/**
* 图片列表
*/
@Schema(description = "普通图片列表")
@NotEmpty(message = "商品图片不能为空")
private List<CustomProductImageSnakeDTO> imageList;
/**
* 尺码图片列表
*/
@Schema(description = "尺码图片列表(type=1)")
private List<CustomProductImageSnakeDTO> sizeList;
/**
* 颜色图
*/
@Schema(description = "颜色图(JSON字符串)")
private String color_images;
/**
* 商品主图
*/
@Schema(description = "商品主图")
@NotBlank(message = "商品主图不能为空")
private String img_url;
/**
* 工厂价格关联列表
*/
@Schema(description = "工厂价格关联列表")
private List<FactoryPriceRelSnakeDTO> factoryPriceList;
/**
* SKU 属性集合
*/
@Schema(description = "SKU 属性集合")
private List<CustomProductInfoPropertyDTO> skuProperties;
/**
* 商品明细
*/
@Schema(description = "商品明细", implementation = CustomProductItemSnakeDTO.class)
@NotEmpty(message = "商品明细不能为空")
private List<CustomProductItemSnakeDTO> productList;
/**
* 工厂价(¥)
*/
@Schema(description = "工厂价(¥)", example = "")
@Digits(integer = 15, fraction = 2, message = "工厂价数值最多保留2位小数")
private BigDecimal factory_price;
/**
* 销售价(¥)
*/
@Schema(description = "销售价(¥)")
@Digits(integer = 15, fraction = 2, message = "工厂价数值最多保留2位小数")
private BigDecimal sales_price;
/**
* 销售价最高价(¥)
*/
@Schema(description = "销售价最高价(¥)")
@Digits(integer = 15, fraction = 2, message = "工厂价数值最多保留2位小数")
private BigDecimal sales_price_max;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sort;
/**
*
*/
@Schema(description = "商品属性ID 1", example = "")
@NotNull(message= "商品属性不能为空")
private Integer property1_cate_id;
/**
*
*/
@Schema(description = "商品属性英文名称 1", example = "")
@NotNull(message= "商品属性名不能为空")
private String property1_enname;
/**
*
*/
@Schema(description = "商品属性ID 2", example = "")
private Integer property2_cate_id;
/**
*
*/
@Schema(description = "商品属性英文名称 2", example = "")
private String property2_enname;
/**
*
*/
@Schema(description = "商品属性ID 3", example = "")
private Integer property3_cate_id;
/**
*
*/
@Schema(description = "商品属性英文名称 3", example = "")
private String property3_enname;
/**
* 重量kg
*/
@Schema(description = "重量(kg)", example = "")
@NotNull(message= "重量不能为空")
@Digits(integer = 15, fraction = 2, message = "重量(kg)数值最多保留2位小数")
private BigDecimal weight;
/**
* 工厂价格区间关联列表
*/
@Schema(description = "工厂价格区间关联列表")
private List<FactoryPriceIntervalRelSnakeDTO> factoryPriceIntervalList;
/**
* DIY 用户 ID 列表(绑定客户)
*/
@Schema(description = "DIY 用户 ID 列表")
private List<Integer> diyUserIds;
/**
* 普通属性集合
*/
@Schema(description = "普通属性集合")
private List<CustomProductInfoPropertyDTO> normalProperties;
/**
* 工厂ID集合
*/
@Schema(description = "工厂ID集合")
private List<Integer> factoryIds;
}
......@@ -21,7 +21,7 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(description = "组合更新商品请求")
public class CustomProductInfoUpdateSnakeDTO extends CustomProductInfoSnakeDTO implements Serializable {
public class CustomProductInfoUpdateSnakeDTO extends CustomProductInfoSaveSnakeDTO {
@Serial
private static final long serialVersionUID = 1L;
......@@ -44,9 +44,6 @@ public class CustomProductInfoUpdateSnakeDTO extends CustomProductInfoSnakeDTO i
@Schema(description = "尺码图变更列表(增/删/改)", implementation = ProductImageChangeDTO.class)
private ProductImageChangeDTO sizeChange;
@Schema(description = "DIY 用户 ID 列表(绑定客户)")
private List<Integer> diyUserIds;
/**
* 工厂价格变更 DTO
*/
......
package com.jomalls.custom.app.exception;
import com.jomalls.custom.enums.CodeEnum;
import lombok.Getter;
import java.io.Serial;
......@@ -40,7 +41,7 @@ public final class ServiceException extends RuntimeException {
}
public ServiceException(String message) {
this.message = message;
this(message, CodeEnum.FAIL.getCode());
}
public ServiceException(String message, Integer code) {
......
package com.jomalls.custom.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.dto.AddBlackListDTO;
import com.jomalls.custom.app.dto.BindDiyUserDTO;
import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO;
import com.jomalls.custom.app.dto.CustomProductInfoUpdateSnakeDTO;
import com.jomalls.custom.app.dto.*;
import com.jomalls.custom.app.vo.CraftCenterSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoPageSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoVO;
import com.jomalls.custom.app.vo.DbDiySnakeVO;
import java.util.List;
......@@ -20,14 +17,14 @@ import java.util.List;
*/
public interface CustomProductInfoService {
IPage<CustomProductInfoSnakeVO> pageList(CustomProductInfoSnakeDTO param);
IPage<CustomProductInfoPageSnakeVO> pageList(CustomProductInfoQuerySnakeDTO param);
/**
* 组合创建商品(SKU 生成 + 事务内写入主表及所有子表)
*
* @param dto 组合创建 DTO
*/
void saveFull(CustomProductInfoSnakeDTO dto);
void saveFull(CustomProductInfoSaveSnakeDTO dto);
/**
* 组合更新商品(事务内处理主表及子表的增/删/改差异)
......@@ -108,7 +105,7 @@ public interface CustomProductInfoService {
* <p>
* 包含黑名单过滤、用户折扣、模板上架状态等 ERP 特定逻辑。
*/
IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param);
IPage<CustomProductInfoPageSnakeVO> erpPage(CustomProductInfoQuerySnakeDTO param);
/**
* ERP 获取绑定 DIY(对齐 TS getBindsDiyByIdAndUserMark)
......
......@@ -2,7 +2,7 @@ package com.jomalls.custom.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.vo.CustomWarehouseInfoPageVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoSnakeVO;
import java.util.List;
......@@ -17,10 +17,10 @@ public interface CustomWarehouseInfoService {
/**
* 列表查询接口
*
* @param customWarehouseInfoVO 条件model
* @param customWarehouseInfoSnakeVO 条件model
* @return list集合
*/
List<CustomWarehouseInfoVO> list(CustomWarehouseInfoVO customWarehouseInfoVO);
List<CustomWarehouseInfoSnakeVO> list(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO);
/**
* 根据条件查询分页列表接口
......@@ -28,7 +28,7 @@ public interface CustomWarehouseInfoService {
* @param customWarehouseInfoPageVO 分页入参model
* @return 分页对象
*/
IPage<CustomWarehouseInfoVO> pageList(CustomWarehouseInfoPageVO customWarehouseInfoPageVO);
IPage<CustomWarehouseInfoSnakeVO> pageList(CustomWarehouseInfoPageVO customWarehouseInfoPageVO);
/**
* 根据id查询详情
......@@ -36,21 +36,21 @@ public interface CustomWarehouseInfoService {
* @param id 主键
* @return 实体model
*/
CustomWarehouseInfoVO info(Long id);
CustomWarehouseInfoSnakeVO info(Long id);
/**
* 保存对象
*
* @param customWarehouseInfoVO 保存对象
* @param customWarehouseInfoSnakeVO 保存对象
*/
void save(CustomWarehouseInfoVO customWarehouseInfoVO);
void save(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO);
/**
* 根据id修改对象
*
* @param customWarehouseInfoVO 修改对象
* @param customWarehouseInfoSnakeVO 修改对象
*/
void updateById(CustomWarehouseInfoVO customWarehouseInfoVO);
void updateById(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO);
/**
* 根据主键ID进行删除
......
......@@ -86,14 +86,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
@Override
public IPage<CustomProductInfoSnakeVO> pageList(CustomProductInfoSnakeDTO param) {
public IPage<CustomProductInfoPageSnakeVO> pageList(CustomProductInfoQuerySnakeDTO param) {
CustomAsserts.nonNull(param, "分页查询参数不能为空");
LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
// 构造查询条件
toQueryWrapper(param, queryWrapper);
IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param);
return page.convert(e -> {
CustomProductInfoSnakeVO snakeVO = BeanMapper.snakeCase().convert(e, CustomProductInfoSnakeVO.class);
CustomProductInfoPageSnakeVO snakeVO = BeanMapper.snakeCase().convert(e, CustomProductInfoPageSnakeVO.class);
if (StringUtils.isNotBlank(e.getColorImages())) {
snakeVO.setColorImageList(Arrays.asList(e.getColorImages().split(",")));
}
......@@ -102,7 +102,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
/** 标准分页查询条件构建(非 ERP) */
private void toQueryWrapper(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
private void toQueryWrapper(CustomProductInfoQuerySnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
toQueryWrapper(param, queryWrapper, false);
}
......@@ -111,7 +111,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
*
* @param isErp true=ERP 模式(title 双字段 OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单过滤)
*/
private void toQueryWrapper(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper, boolean isErp) {
private void toQueryWrapper(CustomProductInfoQuerySnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper, boolean isErp) {
// 分类层级过滤
if (param.getCategory_id() != null) {
List<CategoryInfoModel> cateList = saasAdminService.getAllList();
......@@ -180,7 +180,10 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
queryWrapper.eq(CustomProductInfoEntity::getSku, param.getSku());
}
}
if (param.getStatus() != null) {
// 状态过滤(支持逗号分隔多值,对齐 TS status split(","))
if (param.getStatusList() != null && !param.getStatusList().isEmpty()) {
queryWrapper.in(CustomProductInfoEntity::getStatus, param.getStatusList());
} else if (param.getStatus() != null) {
queryWrapper.eq(CustomProductInfoEntity::getStatus, param.getStatus());
}
if (StringUtils.isNotBlank(param.getProduct_type())) {
......@@ -223,7 +226,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* diyUserId 与 blackUserId 互斥(else if),
* diyUserId=-1 使用 LEFT JOIN 语义查找无绑定的商品。
*/
private void applyDiyAndBlacklistFilter(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
private void applyDiyAndBlacklistFilter(CustomProductInfoQuerySnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
if (param.getDiyUserId() != null) {
if (param.getDiyUserId() == -1) {
// 查找无任何 diy_user 绑定的商品(通过 Domain 层 LEFT JOIN 查询)
......@@ -261,7 +264,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
@Override
public void saveFull(CustomProductInfoSnakeDTO dto) {
public void saveFull(CustomProductInfoSaveSnakeDTO dto) {
CustomAsserts.nonNull(dto, "创建参数不能为空");
// 1. 生成 SKU 调用SysBillRuleService
......@@ -830,9 +833,17 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
@Override
public IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param) {
public IPage<CustomProductInfoPageSnakeVO> erpPage(CustomProductInfoQuerySnakeDTO param) {
CustomAsserts.nonNull(param, "分页查询参数不能为空");
if (StringUtils.isBlank(param.getUserMark())) {
throw new ServiceException("userMark不能为空");
}
if (param.getPrint_type() == null) {
throw new ServiceException("print_type不能为空");
}
if (param.getProcessing() == null) {
throw new ServiceException("processing不能为空");
}
// 1. 查询用户(对齐 TS:683-684)
DbDiyUserEntity user = null;
if (StringUtils.isNotBlank(param.getUserMark())) {
......@@ -842,7 +853,26 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
}
// 2. 构建查询条件
// 2. SKU JM 前缀拆分(对齐 TS:590-607)
// JM 开头的为产品 SKU,其余为 DIY 模板 SKU
if (StringUtils.isNotBlank(param.getSku())) {
List<String> productSkus = new ArrayList<>();
List<String> diySkus = new ArrayList<>();
for (String s : param.getSku().split(",")) {
String trimmed = s.trim();
if (trimmed.startsWith("JM")) {
productSkus.add(trimmed);
} else {
diySkus.add(trimmed);
}
}
param.setSku(productSkus.isEmpty() ? null : String.join(",", productSkus));
if (!diySkus.isEmpty() && StringUtils.isBlank(param.getDiySku())) {
param.setDiySku(String.join(",", diySkus));
}
}
// 3. 构建查询条件
LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
toQueryWrapper(param, queryWrapper, true);
......@@ -908,8 +938,17 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
queryWrapper.in(CustomProductInfoEntity::getId, allowedIds);
}
// 6. 排序(对齐 TS:716-721:sort IS NULL ASC, sort ASC, id DESC)
queryWrapper.orderByAsc(CustomProductInfoEntity::getSort).orderByDesc(CustomProductInfoEntity::getId);
// 6. 排序(对齐 TS:716-721:支持 sort=asc/desc,默认 sort IS NULL ASC, sort ASC, id DESC)
if (StringUtils.isNotBlank(param.getSortDirection())) {
if ("asc".equalsIgnoreCase(param.getSortDirection())) {
queryWrapper.orderByAsc(CustomProductInfoEntity::getId);
} else {
queryWrapper.orderByDesc(CustomProductInfoEntity::getId);
}
} else {
// 默认排序需要 sort IS NULL ASC 确保 NULL 排在非 NULL 之后(对齐 TS Sequelize.literal)
queryWrapper.last(" ORDER BY sort IS NULL ASC, sort ASC, id DESC");
}
// 7. 执行分页查询
IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param);
......@@ -923,7 +962,22 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
diyShelfStatusMap = Collections.emptyMap();
}
// 9. 预计算折扣率(对齐 TS:731-733 setProductExternalPrice)
// 9. 批量加载关联实体(对齐 TS:715 include:[CraftCenter, CustomWarehouseInfo, CustomProductFactoryPriceIntervalRel])
final Map<Integer, List<CraftCenterSnakeVO>> craftMap;
final Map<Integer, List<CustomWarehouseInfoSnakeVO>> warehouseMap;
final Map<Integer, List<FactoryPriceIntervalRelSnakeVO>> intervalMap;
if (!rows.isEmpty()) {
List<Integer> productIds = rows.stream().map(CustomProductInfoEntity::getId).distinct().collect(Collectors.toList());
craftMap = batchLoadCraftsByProductIds(productIds);
warehouseMap = batchLoadWarehousesByProductIds(productIds);
intervalMap = batchLoadFactoryPriceIntervalsByProductIds(productIds);
} else {
craftMap = Collections.emptyMap();
warehouseMap = Collections.emptyMap();
intervalMap = Collections.emptyMap();
}
// 10. 预计算折扣率(对齐 TS:316-355 setProductExternalPrice)
final BigDecimal discountRate;
if (user != null && user.getDiscount() != null) {
discountRate = user.getDiscount().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
......@@ -932,15 +986,17 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
return page.convert(e -> {
CustomProductInfoVO vo = BeanMapper.mapper().convert(e, CustomProductInfoVO.class);
// 应用用户折扣定价
if (discountRate != null) {
if (vo.getSalesPrice() != null) {
vo.setSalesPrice(vo.getSalesPrice().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
if (vo.getSalesPriceMax() != null) {
vo.setSalesPriceMax(vo.getSalesPriceMax().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
CustomProductInfoPageSnakeVO vo = BeanMapper.snakeCase().convert(e, CustomProductInfoPageSnakeVO.class);
// 填充关联实体(对齐 TS include)
vo.setCraftList(craftMap.getOrDefault(e.getId(), Collections.emptyList()));
vo.setWarehouseList(warehouseMap.getOrDefault(e.getId(), Collections.emptyList()));
vo.setFactoryPriceIntervalList(intervalMap.getOrDefault(e.getId(), Collections.emptyList()));
// 应用用户折扣定价(对齐 TS setProductExternalPrice:discount=1 时跳过)
if (discountRate != null && discountRate.compareTo(BigDecimal.ONE) != 0) {
applyDiscountToVO(vo, discountRate);
}
if (StringUtils.isNotBlank(e.getColorImages())) {
vo.setColorImageList(Arrays.asList(e.getColorImages().split(",")));
}
// 标记绑定的 DIY 模板是否已上架
vo.setDiyShelfStatus(diyShelfStatusMap.getOrDefault(e.getDiyId(), false));
......@@ -948,8 +1004,30 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
});
}
/** 折扣逻辑(对齐 TS setProductExternalPrice:主表 + 价格区间 + 工厂价格) */
private void applyDiscountToVO(CustomProductInfoPageSnakeVO vo, BigDecimal discountRate) {
if (vo.getSales_price() != null) {
vo.setSales_price(vo.getSales_price().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
if (vo.getSales_price_max() != null) {
vo.setSales_price_max(vo.getSales_price_max().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
// 价格区间折扣(对齐 TS:342-347)
if (vo.getFactoryPriceIntervalList() != null) {
for (FactoryPriceIntervalRelSnakeVO interval : vo.getFactoryPriceIntervalList()) {
if (interval.getPrice_max() != null) {
interval.setPrice_max(interval.getPrice_max().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
if (interval.getPrice_min() != null) {
interval.setPrice_min(interval.getPrice_min().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
}
}
}
// 注意:erpPage 不加载 productList(TS include 亦不包含),子项折扣仅用于 getByIdOrSku
}
/** 返回空分页结果 */
private IPage<CustomProductInfoVO> emptyPage(CustomProductInfoSnakeDTO param) {
private IPage<CustomProductInfoPageSnakeVO> emptyPage(CustomProductInfoQuerySnakeDTO param) {
return new Page<>(param.getCurrent(), param.getSize());
}
......@@ -977,11 +1055,119 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
/**
* 批量查询工艺关联(对齐 TS erpPage include: CraftCenter → BelongsToMany → CustomProductCraftRel)
* <p>
* 每个 craft VO 嵌套一份 CustomProductCraftRel 数据(product_id + craft_id),
* 对齐 TS Sequelize BelongsToMany 自动包含的 junction 表数据。
*
* @return productId → List&lt;CraftCenterSnakeVO&gt;
*/
private Map<Integer, List<CraftCenterSnakeVO>> batchLoadCraftsByProductIds(List<Integer> productIds) {
if (productIds.isEmpty()) {
return Collections.emptyMap();
}
// 1. 关联表
List<CustomProductCraftRelEntity> rels = customProductCraftRelDomainService.list(
new LambdaQueryWrapper<CustomProductCraftRelEntity>()
.in(CustomProductCraftRelEntity::getProductId, productIds));
if (rels.isEmpty()) {
return Collections.emptyMap();
}
// 2. 工艺实体
List<Long> craftIds = rels.stream().map(CustomProductCraftRelEntity::getCraftId).distinct().collect(Collectors.toList());
List<CraftCenterEntity> crafts = craftCenterDomainService.list(
new LambdaQueryWrapper<CraftCenterEntity>().in(CraftCenterEntity::getId, craftIds));
Map<Long, CraftCenterEntity> craftMap = crafts.stream()
.collect(Collectors.toMap(CraftCenterEntity::getId, c -> c, (a, b) -> a));
// 3. 按 productId 分组,每个 craft VO 独立创建(含 junction 表数据)
return rels.stream().collect(Collectors.groupingBy(
CustomProductCraftRelEntity::getProductId,
Collectors.mapping(r -> {
CraftCenterEntity craft = craftMap.get(r.getCraftId());
if (craft == null) return null;
CraftCenterSnakeVO craftVO = BeanMapper.snakeCase().convert(craft, CraftCenterSnakeVO.class);
// 注入 junction 表数据(对齐 TS CustomProductCraftRel)
CustomProductCraftRelSnakeVO relVO = new CustomProductCraftRelSnakeVO();
relVO.setId(r.getId());
relVO.setProduct_id(r.getProductId());
relVO.setCraft_id(r.getCraftId());
craftVO.setCustomProductCraftRel(relVO);
return craftVO;
}, Collectors.toList())));
}
/**
* 批量查询仓库关联(对齐 TS erpPage include: CustomWarehouseInfo → BelongsToMany → CustomProductWarehouseRel)
*
* @return productId → List&lt;CustomWarehouseInfoSnakeVO&gt;
*/
private Map<Integer, List<CustomWarehouseInfoSnakeVO>> batchLoadWarehousesByProductIds(List<Integer> productIds) {
if (productIds.isEmpty()) {
return Collections.emptyMap();
}
// 1. 关联表
List<CustomProductWarehouseRelEntity> rels = customProductWarehouseRelDomainService.list(
new LambdaQueryWrapper<CustomProductWarehouseRelEntity>()
.in(CustomProductWarehouseRelEntity::getProductId, productIds));
if (rels.isEmpty()) {
return Collections.emptyMap();
}
// 2. 仓库实体
List<Long> wIds = rels.stream().map(CustomProductWarehouseRelEntity::getWarehouseId).distinct().collect(Collectors.toList());
List<CustomWarehouseInfoEntity> warehouses = customWarehouseInfoDomainService.list(
new LambdaQueryWrapper<CustomWarehouseInfoEntity>().in(CustomWarehouseInfoEntity::getId, wIds));
Map<Long, CustomWarehouseInfoEntity> whMap = warehouses.stream()
.collect(Collectors.toMap(CustomWarehouseInfoEntity::getId, w -> w, (a, b) -> a));
// 3. 按 productId 分组,每个 warehouse VO 独立创建(含 junction 表数据)
return rels.stream().collect(Collectors.groupingBy(
CustomProductWarehouseRelEntity::getProductId,
Collectors.mapping(r -> {
CustomWarehouseInfoEntity wh = whMap.get(r.getWarehouseId());
if (wh == null) return null;
CustomWarehouseInfoSnakeVO whVO = BeanMapper.snakeCase().convert(wh, CustomWarehouseInfoSnakeVO.class);
CustomProductWarehouseRelSnakeVO relVO = new CustomProductWarehouseRelSnakeVO();
relVO.setId(r.getId());
relVO.setProduct_id(r.getProductId());
relVO.setWarehouse_id(r.getWarehouseId());
whVO.setCustomProductWarehouseRel(relVO);
return whVO;
}, Collectors.toList())));
}
/**
* 批量查询工厂价格区间关联(对齐 TS erpPage include: CustomProductFactoryPriceIntervalRel → HasMany)
*
* @return productId → List&lt;FactoryPriceIntervalRelSnakeVO&gt;
*/
private Map<Integer, List<FactoryPriceIntervalRelSnakeVO>> batchLoadFactoryPriceIntervalsByProductIds(List<Integer> productIds) {
if (productIds.isEmpty()) {
return Collections.emptyMap();
}
List<CustomProductFactoryPriceIntervalRelEntity> rels = customProductFactoryPriceIntervalRelDomainService.list(
new LambdaQueryWrapper<CustomProductFactoryPriceIntervalRelEntity>()
.in(CustomProductFactoryPriceIntervalRelEntity::getProductId, productIds));
if (rels.isEmpty()) {
return Collections.emptyMap();
}
List<FactoryPriceIntervalRelSnakeVO> voList = rels.stream().map(r -> {
FactoryPriceIntervalRelSnakeVO vo = new FactoryPriceIntervalRelSnakeVO();
vo.setId(r.getId());
vo.setProduct_id(r.getProductId());
vo.setCurrency_code(r.getCurrencyCode());
vo.setPrice_min(r.getPriceMin());
vo.setPrice_max(r.getPriceMax());
return vo;
}).toList();
return voList.stream().collect(Collectors.groupingBy(
FactoryPriceIntervalRelSnakeVO::getProduct_id, Collectors.toList()));
}
/**
* 从事务外的子项列表中计算主表的 factory_price / sales_price / sales_price_max
* <p>
* factory_price 取子项中的最小值,sales_price 取子项中的最大值,sales_price_max 取子项中的最大值。
*/
private void computeProductPricesFromItems(CustomProductInfoSnakeDTO dto) {
private void computeProductPricesFromItems(CustomProductInfoSaveSnakeDTO dto) {
if (dto.getProductList() == null || dto.getProductList().isEmpty()) {
return;
}
......@@ -1021,13 +1207,13 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* 从 SaveDTO 构建 Entity
* <p>
* SaveDTO 使用驼峰命名,与 Entity 字段名一致,直接用默认 mapper 转换。
* 对于蛇形命名的 {@link CustomProductInfoSnakeDTO},调用方需先用 {@code BeanMapper.snakeCase()} 转为 SaveDTO。
* 对于蛇形命名的 {@link CustomProductInfoSaveSnakeDTO},调用方需先用 {@code BeanMapper.snakeCase()} 转为 SaveDTO。
*
* @param dto 保存 DTO(驼峰命名)
* @param sku 生成的 SKU(创建时传入,更新时传 null)
* @return 商品实体
*/
private CustomProductInfoEntity buildEntityFromSaveDTO(CustomProductInfoSnakeDTO dto, String sku) {
private CustomProductInfoEntity buildEntityFromSaveDTO(CustomProductInfoSaveSnakeDTO dto, String sku) {
CustomProductInfoEntity entity = BeanMapper.snakeCase().convert(dto, CustomProductInfoEntity.class);
if (sku != null) {
entity.setSku(sku);
......@@ -1289,7 +1475,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* @return 新增子项的原始 SKU → 已保存实体映射(含自增 ID)
*/
private Map<String, CustomProductItemEntity> handleItemChanges(
ProductChangeSnakeDTO change, Integer productId, CustomProductInfoSnakeDTO dto) {
ProductChangeSnakeDTO change, Integer productId, CustomProductInfoSaveSnakeDTO dto) {
Map<String, CustomProductItemEntity> newItemMap = new HashMap<>();
if (change == null) {
return newItemMap;
......
......@@ -7,7 +7,7 @@ import com.jomalls.custom.app.service.CustomWarehouseInfoService;
import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.utils.CustomAsserts;
import com.jomalls.custom.app.vo.CustomWarehouseInfoPageVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoSnakeVO;
import com.jomalls.custom.dal.entity.CustomWarehouseInfoEntity;
import com.jomalls.custom.domain.service.CustomWarehouseInfoDomainService;
import lombok.extern.slf4j.Slf4j;
......@@ -37,34 +37,34 @@ public class CustomWarehouseInfoServiceImpl implements CustomWarehouseInfoServic
}
@Override
public List<CustomWarehouseInfoVO> list(CustomWarehouseInfoVO customWarehouseInfoVO) {
public List<CustomWarehouseInfoSnakeVO> list(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
QueryWrapper<CustomWarehouseInfoEntity> queryWrapper = new QueryWrapper<>();
// TODO 根据业务条件组装入参
List<CustomWarehouseInfoEntity> list = customWarehouseInfoDomainService.list(queryWrapper);
return list.stream().map(e -> BeanMapper.mapper().convert(e, CustomWarehouseInfoVO.class)).collect(Collectors.toList());
return list.stream().map(e -> BeanMapper.mapper().convert(e, CustomWarehouseInfoSnakeVO.class)).collect(Collectors.toList());
}
@Override
public IPage<CustomWarehouseInfoVO> pageList(CustomWarehouseInfoPageVO customWarehouseInfoPageVO) {
public IPage<CustomWarehouseInfoSnakeVO> pageList(CustomWarehouseInfoPageVO customWarehouseInfoPageVO) {
CustomAsserts.nonNull(customWarehouseInfoPageVO, "分页查询参数不能为空");
QueryWrapper<CustomWarehouseInfoEntity> queryWrapper = new QueryWrapper<>();
// TODO 根据业务条件组装入参
IPage<CustomWarehouseInfoEntity> page = customWarehouseInfoDomainService.selectPage(queryWrapper, customWarehouseInfoPageVO);
return page.convert(e -> BeanMapper.mapper().convert(e, CustomWarehouseInfoVO.class));
return page.convert(e -> BeanMapper.mapper().convert(e, CustomWarehouseInfoSnakeVO.class));
}
@Override
public CustomWarehouseInfoVO info(Long id) {
public CustomWarehouseInfoSnakeVO info(Long id) {
CustomAsserts.nonNull(id, "主键id不能为空");
CustomWarehouseInfoEntity customWarehouseInfo = customWarehouseInfoDomainService.getById(id);
return BeanMapper.mapper().convert(customWarehouseInfo, CustomWarehouseInfoVO.class);
return BeanMapper.mapper().convert(customWarehouseInfo, CustomWarehouseInfoSnakeVO.class);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void save(CustomWarehouseInfoVO customWarehouseInfoVO) {
CustomAsserts.nonNull(customWarehouseInfoVO, "实体对象不能为空");
CustomWarehouseInfoEntity customWarehouseInfoEntity = BeanMapper.mapper().convert(customWarehouseInfoVO, CustomWarehouseInfoEntity.class);
public void save(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
CustomAsserts.nonNull(customWarehouseInfoSnakeVO, "实体对象不能为空");
CustomWarehouseInfoEntity customWarehouseInfoEntity = BeanMapper.mapper().convert(customWarehouseInfoSnakeVO, CustomWarehouseInfoEntity.class);
try {
customWarehouseInfoDomainService.save(customWarehouseInfoEntity);
} catch (DuplicateKeyException e) {
......@@ -75,9 +75,9 @@ public class CustomWarehouseInfoServiceImpl implements CustomWarehouseInfoServic
@Transactional(rollbackFor = Exception.class)
@Override
public void updateById(CustomWarehouseInfoVO customWarehouseInfoVO) {
CustomAsserts.nonNull(customWarehouseInfoVO, "实体对象不能为空");
CustomWarehouseInfoEntity customWarehouseInfo = BeanMapper.mapper().convert(customWarehouseInfoVO, CustomWarehouseInfoEntity.class);
public void updateById(CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
CustomAsserts.nonNull(customWarehouseInfoSnakeVO, "实体对象不能为空");
CustomWarehouseInfoEntity customWarehouseInfo = BeanMapper.mapper().convert(customWarehouseInfoSnakeVO, CustomWarehouseInfoEntity.class);
try {
customWarehouseInfoDomainService.updateById(customWarehouseInfo);
} catch (DuplicateKeyException e) {
......
......@@ -65,4 +65,7 @@ public class CraftCenterSnakeVO implements Serializable {
@Schema(description = "修改时间")
private Date update_time;
@Schema(description = "工艺与商品的关联数据(对齐 TS CustomProductCraftRel)")
private CustomProductCraftRelSnakeVO customProductCraftRel;
}
package com.jomalls.custom.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 工艺关联表 VO(对齐 TS CustomProductCraftRel 实体)
* <p>
* Sequelize BelongsToMany include 时会嵌套在 CraftCenter 对象下。
*
* @author Lizh
* @date 2026-06-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "工艺关联")
public class CustomProductCraftRelSnakeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "关联 ID")
private Integer id;
@Schema(description = "商品 ID")
private Integer product_id;
@Schema(description = "工艺 ID")
private Long craft_id;
}
package com.jomalls.custom.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 商品完整详情 VO
* <p>
* 对齐 TS 项目 {@code entity/custom_product_info.ts} 的返回结构。
* 包含主表字段 + 所有子实体列表,由 App Service 通过并行单表查询后在 Java 层组合。
*
* @author Lizh
* @date 2026-06-06
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "商品完整详情")
public class CustomProductInfoPageSnakeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键 ID")
private Integer id;
@Schema(description = "SKU")
private String sku;
@Schema(description = "货号")
private String product_no;
@Schema(description = "title")
private String title;
@Schema(description = "商品名称")
private String name;
@Schema(description = "图片集")
private List<String> colorImageList;
@Schema(description = "图片集")
private String color_images;
@Schema(description = "商品主图")
private String img_url;
@Schema(description = "商品类别ID")
private Integer category_id;
@Schema(description = "重量(kg)")
private BigDecimal weight;
@Schema(description = "最小采购量")
private Integer purchasing_min;
@Schema(description = "工厂价(¥)")
private BigDecimal factory_price;
@Schema(description = "销售价(¥)")
private BigDecimal sales_price;
@Schema(description = "销售价最高价(¥)")
private BigDecimal sales_price_max;
@Schema(description = "状态:1待上架 10已上架 20已下架 30待下架 40已作废")
private Integer status;
@Schema(description = "商品属性分类ID 1")
private Integer property1_cate_id;
@Schema(description = "商品属性分类ID 2")
private Integer property2_cate_id;
@Schema(description = "商品属性分类ID 3")
private Integer property3_cate_id;
@Schema(description = "商品属性英文名称 1")
private String property1_enname;
@Schema(description = "商品属性英文名称 2")
private String property2_enname;
@Schema(description = "商品属性英文名称 3")
private String property3_enname;
@Schema(description = "材质")
private String material;
@Schema(description = "印花类型:0满印 1局部印")
private Integer print_type;
@Schema(description = "产地编码")
private String origin_code;
@Schema(description = "产地中文名")
private String origin_name_cn;
@Schema(description = "产地英文名")
private String origin_name_en;
@Schema(description = "币种编码")
private String currency_code;
@Schema(description = "币种名称")
private String currency_name;
@Schema(description = "产品类型:platform/customer")
private String product_type;
@Schema(description = "工厂ID")
private Integer factory_id;
@Schema(description = "工厂编码")
private String factory_code;
@Schema(description = "是否九猫处理")
private Boolean processing;
@Schema(description = "创建时间")
private Date create_time;
@Schema(description = "更新时间")
private Date update_time;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "默认模ID")
private Integer diy_id;
@Schema(description = "默认模SKU")
private String diy_sku;
@Schema(description = "普通图片列表(type=0)")
private List<CustomProductImageSnakeVO> imageList;
@Schema(description = "商品描述")
private ProductRemarkVO productRemark;
@Schema(description = "商品中文描述")
private ProductRemarkVO productCnRemark;
@Schema(description = "工厂价格关联列表")
private List<CustomProductFactoryPriceRelSnakeVO> factoryPriceList;
@Schema(description = "尺码图片列表(type=1)")
private List<CustomProductImageSnakeVO> sizeList;
@Schema(description = "DIY 用户 ID 列表")
private List<Integer> diyUserIds;
@Schema(description = "工艺 ID 列表")
private List<String> craftIds;
@Schema(description = "工厂 ID 列表")
private List<Integer> factoryIds;
@Schema(description = "仓库 ID 列表")
private List<Integer> warehouseIds;
@Schema(description = "英文备注内容")
private String remark;
@Schema(description = "中文备注内容")
private String cnRemark;
@Schema(description = "标记绑定的 DIY 模板是否已上架")
private Boolean diyShelfStatus;
@Schema(description = "工厂价格区间关联列表(对齐 TS factoryPriceIntervalList)")
private List<FactoryPriceIntervalRelSnakeVO> factoryPriceIntervalList;
@Schema(description = "工艺列表")
private List<CraftCenterSnakeVO> craftList;
@Schema(description = "仓库列表")
private List<CustomWarehouseInfoSnakeVO> warehouseList;
}
package com.jomalls.custom.app.vo;
import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* PageModel
*
* @author Lizh
* @date 2026-06-03 11:57:02
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(description = "CustomProductInfoPageVo")
public class CustomProductInfoPageVO extends PageRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@Schema(description = "")
private Integer id;
/**
* sku
*/
@Schema(description = "sku")
private String sku;
/**
*
*/
@Schema(description = "")
private String title;
/**
* 商品名称
*/
@Schema(description = "商品名称")
private String name;
/**
* 商品主图
*/
@Schema(description = "商品主图")
private String imgUrl;
/**
* 商品类别ID
*/
@Schema(description = "商品类别ID")
private Integer categoryId;
/**
* 重量kg
*/
@Schema(description = "重量kg")
private BigDecimal weight;
/**
* 最小采购量
*/
@Schema(description = "最小采购量")
private Integer purchasingMin;
/**
* 工厂价(¥)
*/
@Schema(description = "工厂价(¥)")
private BigDecimal factoryPrice;
/**
* 销售价(¥)
*/
@Schema(description = "销售价(¥)")
private BigDecimal salesPrice;
/**
* 销售价最大值(¥)
*/
@Schema(description = "销售价最大值(¥)")
private BigDecimal salesPriceMax;
/**
* 1待上架 10已上架 20已下架 30待下架 40已作废
*/
@Schema(description = "1待上架 10已上架 20已下架 30待下架 40已作废")
private Integer status;
/**
*
*/
@Schema(description = "")
private Integer property1CateId;
/**
*
*/
@Schema(description = "")
private Integer property2CateId;
/**
*
*/
@Schema(description = "")
private Integer property3CateId;
/**
*
*/
@Schema(description = "")
private String property1Enname;
/**
*
*/
@Schema(description = "")
private String property2Enname;
/**
*
*/
@Schema(description = "")
private String property3Enname;
/**
* 颜色图
*/
@Schema(description = "颜色图")
private String colorImages;
/**
* 材质
*/
@Schema(description = "材质")
private String material;
/**
* 印花类型 0满印 1局部印
*/
@Schema(description = "印花类型 0满印 1局部印")
private Integer printType;
/**
* 货号
*/
@Schema(description = "货号")
private String productNo;
/**
* 产地code
*/
@Schema(description = "产地code")
private String originCode;
/**
* 产地中文名字
*/
@Schema(description = "产地中文名字")
private String originNameCn;
/**
* 产地英文名字
*/
@Schema(description = "产地英文名字")
private String originNameEn;
/**
* 币种code
*/
@Schema(description = "币种code")
private String currencyCode;
/**
* 币种名称
*/
@Schema(description = "币种名称")
private String currencyName;
/**
* 产品类型(platform-平台直营 customer-客户自营)
*/
@Schema(description = "产品类型(platform-平台直营 customer-客户自营)")
private String productType;
/**
* 工厂id
*/
@Schema(description = "工厂id")
private Integer factoryId;
/**
* 工厂编码
*/
@Schema(description = "工厂编码")
private String factoryCode;
/**
* 是否九猫处理
*/
@Schema(description = "是否九猫处理")
private Boolean processing;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private Date createTime;
/**
*
*/
@Schema(description = "")
private Date updateTime;
/**
*
*/
@Schema(description = "")
private Integer sort;
/**
* 默认模ID
*/
@Schema(description = "默认模ID")
private Integer diyId;
/**
* 默认模SKU
*/
@Schema(description = "默认模SKU")
private String diySku;
}
......@@ -190,4 +190,17 @@ public class CustomProductInfoSnakeVO implements Serializable {
@Schema(description = "中文备注内容")
private String cnRemark;
@Schema(description = "标记绑定的 DIY 模板是否已上架")
private Boolean diyShelfStatus;
@Schema(description = "工厂价格区间关联列表")
private List<FactoryPriceIntervalRelSnakeVO> factoryPriceIntervalList;
@Schema(description = "标记绑定的 DIY 模板是否已上架")
private List<CraftCenterSnakeVO> craftList;
@Schema(description = "标记绑定的 DIY 模板是否已上架")
private List<CustomWarehouseInfoSnakeVO> warehouseList;
}
package com.jomalls.custom.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* Model
*
* @author Lizh
* @date 2026-06-03 11:57:02
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "VO")
public class CustomProductInfoVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@Schema(description = "")
private Integer id;
/**
* sku
*/
@Schema(description = "sku")
private String sku;
/**
*
*/
@Schema(description = "")
private String title;
/**
* 商品名称
*/
@Schema(description = "商品名称")
private String name;
/**
* 商品主图
*/
@Schema(description = "商品主图")
private String imgUrl;
/**
* 商品类别ID
*/
@Schema(description = "商品类别ID")
private Integer categoryId;
/**
* 重量kg
*/
@Schema(description = "重量kg")
private BigDecimal weight;
/**
* 最小采购量
*/
@Schema(description = "最小采购量")
private Integer purchasingMin;
/**
* 工厂价(¥)
*/
@Schema(description = "工厂价(¥)")
private BigDecimal factoryPrice;
/**
* 销售价(¥)
*/
@Schema(description = "销售价(¥)")
private BigDecimal salesPrice;
/**
* 销售价最大值(¥)
*/
@Schema(description = "销售价最大值(¥)")
private BigDecimal salesPriceMax;
/**
* 1待上架 10已上架 20已下架 30待下架 40已作废
*/
@Schema(description = "1待上架 10已上架 20已下架 30待下架 40已作废")
private Integer status;
/**
*
*/
@Schema(description = "")
private Integer property1CateId;
/**
*
*/
@Schema(description = "")
private Integer property2CateId;
/**
*
*/
@Schema(description = "")
private Integer property3CateId;
/**
*
*/
@Schema(description = "")
private String property1Enname;
/**
*
*/
@Schema(description = "")
private String property2Enname;
/**
*
*/
@Schema(description = "")
private String property3Enname;
/**
* 颜色图
*/
@Schema(description = "颜色图")
private String colorImages;
/**
* 材质
*/
@Schema(description = "材质")
private String material;
/**
* 印花类型 0满印 1局部印
*/
@Schema(description = "印花类型 0满印 1局部印")
private Integer printType;
/**
* 货号
*/
@Schema(description = "货号")
private String productNo;
/**
* 产地code
*/
@Schema(description = "产地code")
private String originCode;
/**
* 产地中文名字
*/
@Schema(description = "产地中文名字")
private String originNameCn;
/**
* 产地英文名字
*/
@Schema(description = "产地英文名字")
private String originNameEn;
/**
* 币种code
*/
@Schema(description = "币种code")
private String currencyCode;
/**
* 币种名称
*/
@Schema(description = "币种名称")
private String currencyName;
/**
* 产品类型(platform-平台直营 customer-客户自营)
*/
@Schema(description = "产品类型(platform-平台直营 customer-客户自营)")
private String productType;
/**
* 工厂id
*/
@Schema(description = "工厂id")
private Integer factoryId;
/**
* 工厂编码
*/
@Schema(description = "工厂编码")
private String factoryCode;
/**
* 是否九猫处理
*/
@Schema(description = "是否九猫处理")
private Boolean processing;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private Date createTime;
/**
*
*/
@Schema(description = "")
private Date updateTime;
/**
*
*/
@Schema(description = "")
private Integer sort;
/**
* 默认模ID
*/
@Schema(description = "默认模ID")
private Integer diyId;
/**
* 默认模SKU
*/
@Schema(description = "默认模SKU")
private String diySku;
@Schema(description = "绑定的DIY模板是否已上架(ERP专用)")
private Boolean diyShelfStatus;
}
package com.jomalls.custom.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 仓库关联表 VO(对齐 TS CustomProductWarehouseRel 实体)
* <p>
* Sequelize BelongsToMany include 时会嵌套在 CustomWarehouseInfo 对象下。
*
* @author Lizh
* @date 2026-06-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "仓库关联")
public class CustomProductWarehouseRelSnakeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "关联 ID")
private Integer id;
@Schema(description = "商品 ID")
private Integer product_id;
@Schema(description = "仓库 ID")
private Long warehouse_id;
}
package com.jomalls.custom.app.vo;
import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
......@@ -19,7 +18,7 @@ import java.util.Date;
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "VO")
public class CustomWarehouseInfoVO implements Serializable {
public class CustomWarehouseInfoSnakeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
......@@ -33,67 +32,67 @@ public class CustomWarehouseInfoVO implements Serializable {
* 仓库名称
*/
@Schema(description = "仓库名称")
private String warehouseName;
private String warehouse_name;
/**
* 仓库编码
*/
@Schema(description = "仓库编码")
private String warehouseCode;
private String warehouse_code;
/**
* 统筹物流
*/
@Schema(description = "统筹物流")
private Boolean overallLogistics;
private Boolean overall_logistics;
/**
* 超级工厂
*/
@Schema(description = "超级工厂")
private Integer superFactory;
private Integer super_factory;
/**
* 系统物流
*/
@Schema(description = "系统物流")
private Boolean systemLogistics;
private Boolean system_logistics;
/**
* 统筹物流覆盖范围
*/
@Schema(description = "统筹物流覆盖范围")
private String overallLogisticsScope;
private String overall_logistics_scope;
/**
* 联系人姓名
*/
@Schema(description = "联系人姓名")
private String contactName;
private String contact_name;
/**
* 联系人电话
*/
@Schema(description = "联系人电话")
private String contactPhone;
private String contact_phone;
/**
* 联系人邮箱
*/
@Schema(description = "联系人邮箱")
private String contactEmail;
private String contact_email;
/**
* 国家代码
*/
@Schema(description = "国家代码")
private String countryCode;
private String country_code;
/**
* 国家名称
*/
@Schema(description = "国家名称")
private String countryName;
private String country_name;
/**
* 州省
......@@ -105,13 +104,13 @@ public class CustomWarehouseInfoVO implements Serializable {
* 州省 code
*/
@Schema(description = "州省 code")
private String provinceCode;
private String province_code;
/**
* 州省简称
*/
@Schema(description = "州省简称")
private String provinceAbb;
private String province_abb;
/**
* 城市
......@@ -123,7 +122,7 @@ public class CustomWarehouseInfoVO implements Serializable {
* 城市编码
*/
@Schema(description = "城市编码")
private String cityCode;
private String city_code;
/**
* 区县
......@@ -147,13 +146,13 @@ public class CustomWarehouseInfoVO implements Serializable {
* 公司名称
*/
@Schema(description = "公司名称")
private String companyName;
private String company_name;
/**
* 社会信用代码
*/
@Schema(description = "社会信用代码")
private String socialCreditCode;
private String social_credit_code;
/**
* 备注
......@@ -165,13 +164,13 @@ public class CustomWarehouseInfoVO implements Serializable {
*
*/
@Schema(description = "")
private Date updateTime;
private Date update_time;
/**
*
*/
@Schema(description = "")
private Date createTime;
private Date create_time;
/**
* 1已上线,10待上线,20已下线,30待下线
......@@ -183,37 +182,37 @@ public class CustomWarehouseInfoVO implements Serializable {
* 联系人姓名(中文)
*/
@Schema(description = "联系人姓名(中文)")
private String contactNameCn;
private String contact_name_cn;
/**
* 国家名称(中文)
*/
@Schema(description = "国家名称(中文)")
private String countryNameCn;
private String country_name_cn;
/**
* 洲省(中文)
*/
@Schema(description = "洲省(中文)")
private String provinceCn;
private String province_cn;
/**
* 城市(中文)
*/
@Schema(description = "城市(中文)")
private String cityCn;
private String city_cn;
/**
* 区县(中文)
*/
@Schema(description = "区县(中文)")
private String districtCn;
private String district_cn;
/**
* 街道(中文)
*/
@Schema(description = "街道(中文)")
private String streetCn;
private String street_cn;
/**
* 是否为正式仓库
......@@ -225,13 +224,15 @@ public class CustomWarehouseInfoVO implements Serializable {
* 币种
*/
@Schema(description = "币种")
private String settlementCurrency;
private String settlement_currency;
/**
* 公司名称(中文)
*/
@Schema(description = "公司名称(中文)")
private String companyNameCn;
private String company_name_cn;
@Schema(description = "仓库与商品的关联数据(对齐 TS CustomProductWarehouseRel)")
private CustomProductWarehouseRelSnakeVO customProductWarehouseRel;
}
package com.jomalls.custom.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 工厂价格区间关联 VO(对齐 TS CustomProductFactoryPriceIntervalRel)
* <p>
* 在 erpPage 等包含关联数据的端点中作为 productInfo.factoryPriceIntervalList 返回。
*
* @author Lizh
* @date 2026-06-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "工厂价格区间关联")
public class FactoryPriceIntervalRelSnakeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "关联 ID")
private Integer id;
@Schema(description = "商品 ID")
private Integer product_id;
@Schema(description = "币种编码")
private String currency_code;
@Schema(description = "系统成本最低价")
private BigDecimal price_min;
@Schema(description = "系统成本最高价")
private BigDecimal price_max;
}
......@@ -37,4 +37,10 @@ public class ProductRemarkVO implements Serializable {
*/
@Schema(description = "图片地址")
private String remark;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private String create_time;
}
package com.jomalls.custom.page;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 分页结果适配转换,将MyBatis-Plus分页结果转为老服务(TS)格式
*
* <pre>
* 映射关系:
* IPage.records → list
* IPage.current → curPage
* IPage.total → totalRow
* IPage.pages → totalPage
* </pre>
*
* @author Lizh
* @date 2026-06-15
*/
@Getter
@AllArgsConstructor
public class PageAdaptter<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 数据列表(对齐老服务 "list") */
private List<T> list;
/** 当前页码(对齐老服务 "curPage") */
private long curPage;
/** 总记录数(对齐老服务 "totalRow") */
private long totalRow;
/** 总页数(对齐老服务 "totalPage") */
private long totalPage;
/** 每页多少条 */
private long size;
/**
* 从 MyBatis-Plus 分页结果构建老服务格式的分页 VO。
*
* @param page MyBatis-Plus 分页对象
* @param <T> 数据类型
* @return 老服务格式的分页结果
*/
public static <T> PageAdaptter<T> from(IPage<T> page) {
return new PageAdaptter<>(
page.getRecords(),
page.getCurrent(),
page.getTotal(),
page.getPages(),
page.getSize()
);
}
}
......@@ -91,7 +91,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler(ServiceException.class)
public ResponseEntity<R<Object>> handleServiceException(ServiceException e) {
log.debug("[ 业务异常 ] code={}, {}", e.getCode(), e.getMessage(), e);
HttpStatus httpStatus = CodeEnum.MAP.getOrDefault(e.getCode(), HttpStatus.INTERNAL_SERVER_ERROR);
HttpStatus httpStatus = CodeEnum.MAP.getOrDefault(e.getCode(), HttpStatus.OK);
return ResponseEntity.status(httpStatus).body(R.fail(e.getCode(), e.getMessage()));
}
......
package com.jomalls.custom.config;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jomalls.custom.enums.CodeEnum;
import com.jomalls.custom.page.PageAdaptter;
import org.jspecify.annotations.NonNull;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
......@@ -23,7 +25,6 @@ import java.util.List;
/**
* 统一返回处理
*
* @author lizh
* @date 2026-05-28 10:25:27
*/
......@@ -60,6 +61,10 @@ public class RestResponseBodyConfig implements ResponseBodyAdvice<Object> {
} else if (this.isNoWrapResponseUrl(requestPath)) {
return body;
} else {
// 分页结果转为老服务(TS)格式:records→list, current→curPage, total→totalRow, pages→totalPage
if (body instanceof IPage<?> page) {
body = PageAdaptter.from(page);
}
if (body instanceof String) {
try {
return objectMapper.writeValueAsString(com.jomalls.custom.utils.R.ok(body));
......
package com.jomalls.custom.webapp.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.dto.AddBlackListDTO;
import com.jomalls.custom.app.dto.BindDiyUserDTO;
import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO;
import com.jomalls.custom.app.dto.CustomProductInfoUpdateSnakeDTO;
import com.jomalls.custom.app.dto.*;
import com.jomalls.custom.app.enums.CustomProductInfoStatusEnum;
import com.jomalls.custom.app.service.CustomProductInfoService;
import com.jomalls.custom.app.vo.CustomProductInfoPageSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoVO;
import com.jomalls.custom.app.vo.CraftCenterSnakeVO;
import com.jomalls.custom.app.vo.DbDiySnakeVO;
import io.swagger.v3.oas.annotations.Operation;
......@@ -32,7 +29,7 @@ import java.util.Map;
@Slf4j
@RestController
@Tag(name = "定制商品管理", description = "定制商品管理接口")
@RequestMapping("/api/v2/product/info")
@RequestMapping("/api/v3/product/info")
@RequiredArgsConstructor
public class CustomProductInfoController {
......@@ -40,7 +37,7 @@ public class CustomProductInfoController {
@Operation(summary = "创建商品", description = "创建商品")
@PostMapping("/create")
public void create(@RequestBody @Valid CustomProductInfoSnakeDTO dto) {
public void create(@RequestBody @Valid CustomProductInfoSaveSnakeDTO dto) {
customProductInfoService.saveFull(dto);
}
......@@ -61,7 +58,7 @@ public class CustomProductInfoController {
@Operation(summary = "分页列表接口", description = "根据条件查询分页列表接口")
@RequestMapping(value = "/page", method = RequestMethod.POST)
public IPage<CustomProductInfoSnakeVO> pageList(@RequestBody CustomProductInfoSnakeDTO param) {
public IPage<CustomProductInfoPageSnakeVO> pageList(@RequestBody CustomProductInfoQuerySnakeDTO param) {
return customProductInfoService.pageList(param);
}
......@@ -119,7 +116,7 @@ public class CustomProductInfoController {
@Operation(summary = "ERP 分页查询", description = "包含黑名单过滤、用户折扣等 ERP 特定逻辑")
@PostMapping("/erpPage")
public IPage<CustomProductInfoVO> erpPage(@RequestBody CustomProductInfoSnakeDTO param) {
public IPage<CustomProductInfoPageSnakeVO> erpPage(@RequestBody CustomProductInfoQuerySnakeDTO param) {
return customProductInfoService.erpPage(param);
}
......
......@@ -25,7 +25,7 @@ import java.util.List;
@Slf4j
@RestController
@Tag(name = "定制商品明细", description = "定制商品明细接口")
@RequestMapping("/api/v2/product/item")
@RequestMapping("/api/v3/product/item")
@RequiredArgsConstructor
public class CustomProductItemController {
......
......@@ -3,7 +3,7 @@ package com.jomalls.custom.webapp.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.service.CustomWarehouseInfoService;
import com.jomalls.custom.app.vo.CustomWarehouseInfoPageVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoVO;
import com.jomalls.custom.app.vo.CustomWarehouseInfoSnakeVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
......@@ -33,13 +33,13 @@ public class CustomWarehouseInfoController {
/**
* 列表查询接口
*
* @param customWarehouseInfoVO 条件model
* @param customWarehouseInfoSnakeVO 条件model
* @return list集合
*/
@Operation(summary = "列表查询接口", description = "根据条件查询列表接口(不分页)")
@RequestMapping(value = "/list", method = RequestMethod.POST)
public List<CustomWarehouseInfoVO> list(@RequestBody CustomWarehouseInfoVO customWarehouseInfoVO) {
return customWarehouseInfoService.list(customWarehouseInfoVO);
public List<CustomWarehouseInfoSnakeVO> list(@RequestBody CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
return customWarehouseInfoService.list(customWarehouseInfoSnakeVO);
}
/**
......@@ -50,7 +50,7 @@ public class CustomWarehouseInfoController {
*/
@Operation(summary = "分页列表接口", description = "根据条件查询分页列表接口")
@RequestMapping(value = "/pageList", method = RequestMethod.POST)
public IPage<CustomWarehouseInfoVO> pageList(@RequestBody CustomWarehouseInfoPageVO customWarehouseInfoPageVO) {
public IPage<CustomWarehouseInfoSnakeVO> pageList(@RequestBody CustomWarehouseInfoPageVO customWarehouseInfoPageVO) {
return customWarehouseInfoService.pageList(customWarehouseInfoPageVO);
}
......@@ -63,31 +63,31 @@ public class CustomWarehouseInfoController {
*/
@Operation(summary = "根据主键id查询详情", description = "根据主键id查询详情")
@RequestMapping(value = "/info/{id}", method = RequestMethod.GET)
public CustomWarehouseInfoVO info(@Parameter(description = "主键id", required = true) @PathVariable("id") Long id) {
public CustomWarehouseInfoSnakeVO info(@Parameter(description = "主键id", required = true) @PathVariable("id") Long id) {
return customWarehouseInfoService.info(id);
}
/**
* 保存对象
*
* @param customWarehouseInfoVO 保存对象
* @param customWarehouseInfoSnakeVO 保存对象
*/
@Operation(summary = "保存对象", description = "保存对象")
@RequestMapping(value = "/save", method = RequestMethod.POST)
public void save(@RequestBody @Valid CustomWarehouseInfoVO customWarehouseInfoVO) {
customWarehouseInfoService.save(customWarehouseInfoVO);
public void save(@RequestBody @Valid CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
customWarehouseInfoService.save(customWarehouseInfoSnakeVO);
}
/**
* 根据id修改对象
*
* @param customWarehouseInfoVO 修改对象
* @param customWarehouseInfoSnakeVO 修改对象
*/
@Operation(summary = "根据id修改对象", description = "根据id修改对象")
@RequestMapping(value = "/updateById", method = RequestMethod.PUT)
public void updateById(@RequestBody CustomWarehouseInfoVO customWarehouseInfoVO) {
customWarehouseInfoService.updateById(customWarehouseInfoVO);
public void updateById(@RequestBody CustomWarehouseInfoSnakeVO customWarehouseInfoSnakeVO) {
customWarehouseInfoService.updateById(customWarehouseInfoSnakeVO);
}
/**
......
......@@ -2,7 +2,10 @@ package com.jomalls.custom.webapp.controller;
import com.jomalls.custom.app.constant.Constants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
......@@ -14,6 +17,9 @@ import java.util.Map;
* @Description: 心跳检测接口
* @Version: 1.0
*/
@Slf4j
@Tag(name = "服务健康检查", description = "服务健康检查")
@RequestMapping("/actuator")
@RestController
public class HealthController {
/**
......@@ -29,7 +35,7 @@ public class HealthController {
* 健康检查接口,返回UP状态
*/
@Operation(summary = "健康检测接口", description = "健康检测接口")
@GetMapping(value = "/actuator/health")
@GetMapping(value = "/health")
public Map<String, String> check() {
Map<String, String> map = new HashMap<>(Constants.DEFAULT_INITIAL_CAPACITY);
map.put(HEALTH_STATUS_KEY, HEALTH_STATUS_UP);
......
......@@ -24,7 +24,7 @@ import java.util.List;
@Slf4j
@RestController
@Tag(name = "商品日志管理", description = "商品日志管理接口")
@RequestMapping("/api/v2/product/log")
@RequestMapping("/api/v3/product/log")
@RequiredArgsConstructor
public class LogCustomProductController {
......
......@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@Tag(name = "SKU编号生成", description = "生成产品SKU编号")
@RequestMapping("/api/v2/product/homesku")
@RequestMapping("/api/v3/product/homesku")
@RequiredArgsConstructor
public class SysBillRuleController {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment