Commit 43164dcf by Lizh

对照TS代码逻辑,迁移商品更新接口

parent 7b931415
package com.jomalls.custom.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 组合创建商品 DTO
* <p>
* 对齐 TS 项目 {@code dto/custom.product.dto.ts} 中的 {@code CreateCustomProductInfoDTO}。
* 包含商品基本信息 + 所有子实体列表,由 App Service 编排写入多张表。
*
* @author Lizh
* @date 2026-06-06
*/
@Data
@Schema(description = "组合创建商品请求")
public class CustomProductInfoSaveDTO1 implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "商品名称")
@NotNull(message = "商品名称不能为空")
@Size(max = 255, message = "商品名称长度不能超过255个字符")
private String name;
@Schema(description = "title")
@Size(max = 255, message = "title长度不能超过255个字符")
private String title;
@Schema(description = "商品主图")
private String imgUrl;
@Schema(description = "商品类别ID")
private Integer categoryId;
@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;
@Schema(description = "状态:1待上架 10已上架 20已下架 30待下架 40已作废")
private Integer status;
@Schema(description = "商品属性分类ID 1")
private Integer property1CateId;
@Schema(description = "商品属性分类ID 2")
private Integer property2CateId;
@Schema(description = "商品属性分类ID 3")
private Integer property3CateId;
@Schema(description = "商品属性英文名称 1")
private String property1Enname;
@Schema(description = "商品属性英文名称 2")
private String property2Enname;
@Schema(description = "商品属性英文名称 3")
private String property3Enname;
@Schema(description = "颜色图(JSON字符串)")
private String colorImages;
@Schema(description = "材质")
private String material;
@Schema(description = "印花类型:0满印 1局部印")
private Integer printType;
@Schema(description = "货号")
@Size(max = 20, message = "货号长度不能超过20个字符")
private String productNo;
@Schema(description = "产地编码")
private String originCode;
@Schema(description = "产地中文名")
private String originNameCn;
@Schema(description = "产地英文名")
private String originNameEn;
@Schema(description = "币种编码")
private String currencyCode;
@Schema(description = "币种名称")
private String currencyName;
@Schema(description = "产品类型:platform/customer")
private String productType;
@Schema(description = "工厂ID")
private Integer factoryId;
@Schema(description = "是否九猫处理")
private Integer processing;
@Schema(description = "排序")
private Integer sort;
// ==================== 子实体列表 ====================
@Schema(description = "SKU 子项列表")
private List<CustomProductItemSnakeDTO> productList;
@Schema(description = "普通图片列表")
private List<CustomProductImageSnakeDTO> imageList;
@Schema(description = "尺码图片列表(type=1)")
private List<CustomProductImageSnakeDTO> sizeList;
@Schema(description = "SKU 属性集合")
private List<CustomProductInfoPropertyDTO> skuProperties;
@Schema(description = "普通属性集合")
private List<CustomProductInfoPropertyDTO> normalProperties;
@Schema(description = "工厂价格关联列表")
private List<FactoryPriceRelDTO> factoryPriceList;
@Schema(description = "工厂 ID 列表")
private List<Integer> factoryIds;
@Schema(description = "仓库 ID 列表")
private List<Integer> warehouseIds;
@Schema(description = "DIY 用户 ID 列表")
private List<Integer> diyUserIds;
@Schema(description = "工艺 ID 列表")
private List<Integer> craftIds;
@Schema(description = "工厂价格区间关联列表")
private List<FactoryPriceIntervalRelDTO> factoryPriceIntervalList;
@Schema(description = "英文备注")
private String remark;
@Schema(description = "中文备注")
private String cnRemark;
}
package com.jomalls.custom.app.dto; package com.jomalls.custom.app.dto;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.jomalls.custom.page.PageRequest; import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.Digits;
...@@ -135,10 +136,24 @@ public class CustomProductInfoSnakeDTO extends PageRequest { ...@@ -135,10 +136,24 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
private Integer status; private Integer status;
/** /**
* 是否九猫处理 * 是否九猫处理(0=否 1=是 2=不确定/未设置)
* <p>
* 兼容前端发送 Boolean(true→1, false→0)和 Number(0/1/2)。
*/ */
@Schema(description = "是否九猫处理") @Schema(description = "是否九猫处理(0=否 1=是 2=未设置,也支持 true/false)")
private Boolean processing; private Integer processing;
/** Jackson 兼容 Boolean→Integer 反序列化 */
@JsonSetter("processing")
public void setProcessingValue(Object value) {
if (value instanceof Boolean) {
this.processing = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.processing = ((Number) value).intValue();
} else {
this.processing = null;
}
}
/** /**
* 英文备注 * 英文备注
...@@ -180,7 +195,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest { ...@@ -180,7 +195,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
* 工厂价格关联列表 * 工厂价格关联列表
*/ */
@Schema(description = "工厂价格关联列表") @Schema(description = "工厂价格关联列表")
private List<FactoryPriceRelDTO> factoryPriceList; private List<FactoryPriceRelSnakeDTO> factoryPriceList;
/** /**
* SKU 属性集合 * SKU 属性集合
...@@ -271,7 +286,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest { ...@@ -271,7 +286,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
* 工厂价格区间关联列表 * 工厂价格区间关联列表
*/ */
@Schema(description = "工厂价格区间关联列表") @Schema(description = "工厂价格区间关联列表")
private List<FactoryPriceIntervalRelDTO> factoryPriceIntervalList; private List<FactoryPriceIntervalRelSnakeDTO> factoryPriceIntervalList;
/** /**
* DIY 用户 ID 列表(绑定客户) * DIY 用户 ID 列表(绑定客户)
...@@ -296,8 +311,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest { ...@@ -296,8 +311,7 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
@Schema(description = "工厂 ID 列表") @Schema(description = "工厂 ID 列表")
private List<Integer> factoryIds; private List<Integer> factoryIds;
// ==================== ERP 专用查询字段(对齐 TS ERPQueryProductInfoDTO) ==================== /** 以下为ERP 专用查询字段 */
@Schema(description = "DIY 模板 SKU 筛选(ERP)") @Schema(description = "DIY 模板 SKU 筛选(ERP)")
private String diySku; private String diySku;
......
package com.jomalls.custom.app.dto; package com.jomalls.custom.app.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
...@@ -15,12 +16,12 @@ import java.util.List; ...@@ -15,12 +16,12 @@ import java.util.List;
* 继承 SaveDTO 的字段,额外增加变更列表用于差异化更新。 * 继承 SaveDTO 的字段,额外增加变更列表用于差异化更新。
* *
* @author Lizh * @author Lizh
* @date 2026-06-06 * @date 2026-06-10
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@Schema(description = "组合更新商品请求") @Schema(description = "组合更新商品请求")
public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implements Serializable { public class CustomProductInfoUpdateSnakeDTO extends CustomProductInfoSnakeDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -28,17 +29,23 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem ...@@ -28,17 +29,23 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem
@Schema(description = "商品 ID(必填)") @Schema(description = "商品 ID(必填)")
private Integer id; private Integer id;
@Schema(description = "子项变更列表(增/删/改)", implementation = ProductChangeDTO.class) @Schema(description = "子项变更列表(增/删/改)", implementation = ProductChangeSnakeDTO.class)
private ProductChangeDTO productChange; private ProductChangeSnakeDTO productChange;
@JsonProperty("factoryPriceChange")
@Schema(description = "工厂价格变更列表(增/删/改)", implementation = ProductFactoryPriceChangeDTO.class) @Schema(description = "工厂价格变更列表(增/删/改)", implementation = ProductFactoryPriceChangeDTO.class)
private ProductFactoryPriceChangeDTO productFactoryPriceChange; private ProductFactoryPriceChangeDTO factoryPriceChange;
@JsonProperty("imageChange")
@Schema(description = "图片变更列表(增/删/改)", implementation = ProductImageChangeDTO.class) @Schema(description = "图片变更列表(增/删/改)", implementation = ProductImageChangeDTO.class)
private ProductImageChangeDTO productImageChange; private ProductImageChangeDTO imageChange;
@JsonProperty("sizeChange")
@Schema(description = "尺码图变更列表(增/删/改)", implementation = ProductImageChangeDTO.class) @Schema(description = "尺码图变更列表(增/删/改)", implementation = ProductImageChangeDTO.class)
private ProductImageChangeDTO productSizeChange; private ProductImageChangeDTO sizeChange;
@Schema(description = "DIY 用户 ID 列表(绑定客户)")
private List<Integer> diyUserIds;
/** /**
* 工厂价格变更 DTO * 工厂价格变更 DTO
...@@ -46,13 +53,14 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem ...@@ -46,13 +53,14 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem
@Data @Data
@Schema(description = "工厂价格变更") @Schema(description = "工厂价格变更")
public static class ProductFactoryPriceChangeDTO implements Serializable { public static class ProductFactoryPriceChangeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "新增列表") @Schema(description = "新增列表")
private List<FactoryPriceRelDTO> addList; private List<FactoryPriceRelSnakeDTO> addList;
@Schema(description = "修改列表") @Schema(description = "修改列表")
private List<FactoryPriceRelDTO> updateList; private List<FactoryPriceRelSnakeDTO> updateList;
@Schema(description = "删除 ID 列表") @Schema(description = "删除 ID 列表")
private List<Integer> removeList; private List<Integer> removeList;
...@@ -64,11 +72,15 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem ...@@ -64,11 +72,15 @@ public class CustomProductInfoUpdateDTO extends CustomProductInfoSaveDTO1 implem
@Data @Data
@Schema(description = "图片变更") @Schema(description = "图片变更")
public static class ProductImageChangeDTO implements Serializable { public static class ProductImageChangeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "新增列表") @Schema(description = "新增列表")
private List<CustomProductImageSnakeDTO> addList; private List<CustomProductImageSnakeDTO> addList;
@Schema(description = "修改列表")
private List<CustomProductImageSnakeDTO> updateList;
@Schema(description = "删除 ID 列表") @Schema(description = "删除 ID 列表")
private List<Integer> removeList; private List<Integer> removeList;
} }
......
...@@ -6,6 +6,7 @@ import lombok.Builder; ...@@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
...@@ -15,23 +16,24 @@ import java.math.BigDecimal; ...@@ -15,23 +16,24 @@ import java.math.BigDecimal;
* 对齐 TS 项目 {@code dto/custom.product.dto.ts} 中的 {@code FactoryPriceIntervalRelDTO}。 * 对齐 TS 项目 {@code dto/custom.product.dto.ts} 中的 {@code FactoryPriceIntervalRelDTO}。
* *
* @author Lizh * @author Lizh
* @date 2026-06-06 * @date 2026-06-10
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "工厂价格区间关联") @Schema(description = "工厂价格区间关联")
public class FactoryPriceIntervalRelDTO implements Serializable { public class FactoryPriceIntervalRelSnakeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "币种编码") @Schema(description = "币种编码")
private String currencyCode; private String currency_code;
@Schema(description = "系统成本最低价") @Schema(description = "系统成本最低价")
private BigDecimal priceMin; private BigDecimal price_min;
@Schema(description = "系统成本最高价") @Schema(description = "系统成本最高价")
private BigDecimal priceMax; private BigDecimal price_max;
} }
...@@ -16,36 +16,36 @@ import java.math.BigDecimal; ...@@ -16,36 +16,36 @@ import java.math.BigDecimal;
* 对齐 TS 项目 {@code dto/custom.product.dto.ts} 中的 {@code CreateFactoryPriceRelDTO}。 * 对齐 TS 项目 {@code dto/custom.product.dto.ts} 中的 {@code CreateFactoryPriceRelDTO}。
* *
* @author Lizh * @author Lizh
* @date 2026-06-06 * @date 2026-06-10
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "工厂价格关联") @Schema(description = "工厂价格关联")
public class FactoryPriceRelDTO implements Serializable { public class FactoryPriceRelSnakeDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "子项 ID(custom_product_item 的 id)") @Schema(description = "子项 ID(custom_product_item 的 id)")
private Integer itemId; private Integer item_id;
@Schema(description = "子项 SKU") @Schema(description = "子项 SKU")
private String itemSku; private String item_sku;
@Schema(description = "工厂 ID") @Schema(description = "工厂 ID")
private Integer factoryId; private Integer factory_id;
@Schema(description = "工厂价格") @Schema(description = "工厂价格")
private BigDecimal factoryPrice; private BigDecimal factory_price;
@Schema(description = "销售价格") @Schema(description = "销售价格")
private BigDecimal salesPrice; private BigDecimal sales_price;
@Schema(description = "工厂币种编码") @Schema(description = "工厂币种编码")
private String factoryCurrencyCode; private String factory_currency_code;
@Schema(description = "销售币种编码") @Schema(description = "销售币种编码")
private String salesCurrencyCode; private String sales_currency_code;
} }
...@@ -14,7 +14,7 @@ import java.util.List; ...@@ -14,7 +14,7 @@ import java.util.List;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "商品变更Dto") @Schema(description = "商品变更Dto")
public class ProductChangeDTO implements Serializable { public class ProductChangeSnakeDTO implements Serializable {
@Schema(description = "添加集合", implementation = CustomProductItemSnakeDTO.class) @Schema(description = "添加集合", implementation = CustomProductItemSnakeDTO.class)
private List<CustomProductItemSnakeDTO> addList; private List<CustomProductItemSnakeDTO> addList;
......
...@@ -2,7 +2,7 @@ package com.jomalls.custom.app.service; ...@@ -2,7 +2,7 @@ package com.jomalls.custom.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelSnakeVO;
import java.util.List; import java.util.List;
...@@ -17,10 +17,10 @@ public interface CustomProductFactoryPriceRelService { ...@@ -17,10 +17,10 @@ public interface CustomProductFactoryPriceRelService {
/** /**
* 列表查询接口 * 列表查询接口
* *
* @param customProductFactoryPriceRelVO 条件model * @param customProductFactoryPriceRelSnakeVO 条件model
* @return list集合 * @return list集合
*/ */
List<CustomProductFactoryPriceRelVO> list(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO); List<CustomProductFactoryPriceRelSnakeVO> list(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO);
/** /**
* 根据条件查询分页列表接口 * 根据条件查询分页列表接口
...@@ -28,7 +28,7 @@ public interface CustomProductFactoryPriceRelService { ...@@ -28,7 +28,7 @@ public interface CustomProductFactoryPriceRelService {
* @param customProductFactoryPriceRelPageVO 分页入参model * @param customProductFactoryPriceRelPageVO 分页入参model
* @return 分页对象 * @return 分页对象
*/ */
IPage<CustomProductFactoryPriceRelVO> pageList(CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO); IPage<CustomProductFactoryPriceRelSnakeVO> pageList(CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO);
/** /**
* 根据id查询详情 * 根据id查询详情
...@@ -36,21 +36,21 @@ public interface CustomProductFactoryPriceRelService { ...@@ -36,21 +36,21 @@ public interface CustomProductFactoryPriceRelService {
* @param id 主键 * @param id 主键
* @return 实体model * @return 实体model
*/ */
CustomProductFactoryPriceRelVO info(Integer id); CustomProductFactoryPriceRelSnakeVO info(Integer id);
/** /**
* 保存对象 * 保存对象
* *
* @param customProductFactoryPriceRelVO 保存对象 * @param customProductFactoryPriceRelSnakeVO 保存对象
*/ */
void save(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO); void save(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO);
/** /**
* 根据id修改对象 * 根据id修改对象
* *
* @param customProductFactoryPriceRelVO 修改对象 * @param customProductFactoryPriceRelSnakeVO 修改对象
*/ */
void updateById(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO); void updateById(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO);
/** /**
* 根据主键ID进行删除 * 根据主键ID进行删除
......
...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; ...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.dto.AddBlackListDTO; import com.jomalls.custom.app.dto.AddBlackListDTO;
import com.jomalls.custom.app.dto.BindDiyUserDTO; import com.jomalls.custom.app.dto.BindDiyUserDTO;
import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO; import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO;
import com.jomalls.custom.app.dto.CustomProductInfoUpdateDTO; import com.jomalls.custom.app.dto.CustomProductInfoUpdateSnakeDTO;
import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO; import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO;
import com.jomalls.custom.app.vo.CustomProductInfoVO; import com.jomalls.custom.app.vo.CustomProductInfoVO;
import com.jomalls.custom.app.vo.DbDiyVO; import com.jomalls.custom.app.vo.DbDiyVO;
...@@ -33,7 +33,7 @@ public interface CustomProductInfoService { ...@@ -33,7 +33,7 @@ public interface CustomProductInfoService {
* *
* @param dto 组合更新 DTO * @param dto 组合更新 DTO
*/ */
void updateFull(CustomProductInfoUpdateDTO dto); void updateFull(CustomProductInfoUpdateSnakeDTO dto);
/** /**
* 根据 ID 或 SKU 加载商品完整数据(并行单表查询后在 Java 层组合) * 根据 ID 或 SKU 加载商品完整数据(并行单表查询后在 Java 层组合)
......
...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; ...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.exception.ServiceException; import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelSnakeVO;
import com.jomalls.custom.app.service.CustomProductFactoryPriceRelService; import com.jomalls.custom.app.service.CustomProductFactoryPriceRelService;
import com.jomalls.custom.app.utils.BeanMapper; import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.utils.CustomAsserts; import com.jomalls.custom.app.utils.CustomAsserts;
...@@ -37,34 +37,34 @@ public class CustomProductFactoryPriceRelServiceImpl implements CustomProductFac ...@@ -37,34 +37,34 @@ public class CustomProductFactoryPriceRelServiceImpl implements CustomProductFac
} }
@Override @Override
public List<CustomProductFactoryPriceRelVO> list(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public List<CustomProductFactoryPriceRelSnakeVO> list(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
QueryWrapper<CustomProductFactoryPriceRelEntity> queryWrapper = new QueryWrapper<>(); QueryWrapper<CustomProductFactoryPriceRelEntity> queryWrapper = new QueryWrapper<>();
// TODO 根据业务条件组装入参 // TODO 根据业务条件组装入参
List<CustomProductFactoryPriceRelEntity> list = customProductFactoryPriceRelDomainService.list(queryWrapper); List<CustomProductFactoryPriceRelEntity> list = customProductFactoryPriceRelDomainService.list(queryWrapper);
return list.stream().map(e -> BeanMapper.mapper().convert(e, CustomProductFactoryPriceRelVO.class)).collect(Collectors.toList()); return list.stream().map(e -> BeanMapper.mapper().convert(e, CustomProductFactoryPriceRelSnakeVO.class)).collect(Collectors.toList());
} }
@Override @Override
public IPage<CustomProductFactoryPriceRelVO> pageList(CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO) { public IPage<CustomProductFactoryPriceRelSnakeVO> pageList(CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO) {
CustomAsserts.nonNull(customProductFactoryPriceRelPageVO, "分页查询参数不能为空"); CustomAsserts.nonNull(customProductFactoryPriceRelPageVO, "分页查询参数不能为空");
QueryWrapper<CustomProductFactoryPriceRelEntity> queryWrapper = new QueryWrapper<>(); QueryWrapper<CustomProductFactoryPriceRelEntity> queryWrapper = new QueryWrapper<>();
// TODO 根据业务条件组装入参 // TODO 根据业务条件组装入参
IPage<CustomProductFactoryPriceRelEntity> page = customProductFactoryPriceRelDomainService.selectPage(queryWrapper, customProductFactoryPriceRelPageVO); IPage<CustomProductFactoryPriceRelEntity> page = customProductFactoryPriceRelDomainService.selectPage(queryWrapper, customProductFactoryPriceRelPageVO);
return page.convert(e -> BeanMapper.mapper().convert(e, CustomProductFactoryPriceRelVO.class)); return page.convert(e -> BeanMapper.mapper().convert(e, CustomProductFactoryPriceRelSnakeVO.class));
} }
@Override @Override
public CustomProductFactoryPriceRelVO info(Integer id) { public CustomProductFactoryPriceRelSnakeVO info(Integer id) {
CustomAsserts.nonNull(id, "主键id不能为空"); CustomAsserts.nonNull(id, "主键id不能为空");
CustomProductFactoryPriceRelEntity customProductFactoryPriceRel = customProductFactoryPriceRelDomainService.getById(id); CustomProductFactoryPriceRelEntity customProductFactoryPriceRel = customProductFactoryPriceRelDomainService.getById(id);
return BeanMapper.mapper().convert(customProductFactoryPriceRel, CustomProductFactoryPriceRelVO.class); return BeanMapper.mapper().convert(customProductFactoryPriceRel, CustomProductFactoryPriceRelSnakeVO.class);
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void save(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public void save(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
CustomAsserts.nonNull(customProductFactoryPriceRelVO, "实体对象不能为空"); CustomAsserts.nonNull(customProductFactoryPriceRelSnakeVO, "实体对象不能为空");
CustomProductFactoryPriceRelEntity customProductFactoryPriceRelEntity = BeanMapper.mapper().convert(customProductFactoryPriceRelVO, CustomProductFactoryPriceRelEntity.class); CustomProductFactoryPriceRelEntity customProductFactoryPriceRelEntity = BeanMapper.mapper().convert(customProductFactoryPriceRelSnakeVO, CustomProductFactoryPriceRelEntity.class);
try { try {
customProductFactoryPriceRelDomainService.save(customProductFactoryPriceRelEntity); customProductFactoryPriceRelDomainService.save(customProductFactoryPriceRelEntity);
} catch (DuplicateKeyException e) { } catch (DuplicateKeyException e) {
...@@ -75,9 +75,9 @@ public class CustomProductFactoryPriceRelServiceImpl implements CustomProductFac ...@@ -75,9 +75,9 @@ public class CustomProductFactoryPriceRelServiceImpl implements CustomProductFac
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void updateById(CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public void updateById(CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
CustomAsserts.nonNull(customProductFactoryPriceRelVO, "实体对象不能为空"); CustomAsserts.nonNull(customProductFactoryPriceRelSnakeVO, "实体对象不能为空");
CustomProductFactoryPriceRelEntity customProductFactoryPriceRel = BeanMapper.mapper().convert(customProductFactoryPriceRelVO, CustomProductFactoryPriceRelEntity.class); CustomProductFactoryPriceRelEntity customProductFactoryPriceRel = BeanMapper.mapper().convert(customProductFactoryPriceRelSnakeVO, CustomProductFactoryPriceRelEntity.class);
try { try {
customProductFactoryPriceRelDomainService.updateById(customProductFactoryPriceRel); customProductFactoryPriceRelDomainService.updateById(customProductFactoryPriceRel);
} catch (DuplicateKeyException e) { } catch (DuplicateKeyException e) {
......
...@@ -4,10 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; ...@@ -4,10 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.dto.*; import com.jomalls.custom.app.dto.*;
import com.jomalls.custom.app.enums.SkuGenerateEnums; import com.jomalls.custom.app.enums.SkuGenerateEnums;
import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.enums.TemplateStatus; import com.jomalls.custom.app.enums.TemplateStatus;
import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.service.CustomProductInfoService; import com.jomalls.custom.app.service.CustomProductInfoService;
import com.jomalls.custom.app.service.DiyUserService; import com.jomalls.custom.app.service.DiyUserService;
import com.jomalls.custom.app.service.SysBillRuleService; import com.jomalls.custom.app.service.SysBillRuleService;
...@@ -20,11 +21,12 @@ import com.jomalls.custom.integrate.model.BaseCategoryInfoModel; ...@@ -20,11 +21,12 @@ import com.jomalls.custom.integrate.model.BaseCategoryInfoModel;
import com.jomalls.custom.integrate.model.BasePropertyModel; import com.jomalls.custom.integrate.model.BasePropertyModel;
import com.jomalls.custom.integrate.model.BasePropertyValueModel; import com.jomalls.custom.integrate.model.BasePropertyValueModel;
import com.jomalls.custom.integrate.service.SaasAdminService; import com.jomalls.custom.integrate.service.SaasAdminService;
import com.jomalls.custom.security.LoginUser;
import com.jomalls.custom.security.SecurityUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
...@@ -91,7 +93,17 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -91,7 +93,17 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
return page.convert(e -> BeanMapper.mapper().convert(e, CustomProductInfoVO.class)); return page.convert(e -> BeanMapper.mapper().convert(e, CustomProductInfoVO.class));
} }
/** 标准分页查询条件构建(非 ERP) */
private void toQueryWrapper(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper) { private void toQueryWrapper(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper) {
toQueryWrapper(param, queryWrapper, false);
}
/**
* 查询条件构建
*
* @param isErp true=ERP 模式(title 双字段 OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单过滤)
*/
private void toQueryWrapper(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper, boolean isErp) {
// 分类层级过滤 // 分类层级过滤
if (param.getCategory_id() != null) { if (param.getCategory_id() != null) {
List<BaseCategoryInfoModel> cateList = saasAdminService.getAllList(); List<BaseCategoryInfoModel> cateList = saasAdminService.getAllList();
...@@ -120,10 +132,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -120,10 +132,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
// 是否九猫处理过滤 // 是否九猫处理过滤
if (param.getProcessing()) { Integer processing = param.getProcessing();
queryWrapper.eq("processing", 1); if (processing != null) {
if (processing == 2) {
// ERP: processing=2 → IS NULL(对齐 TS:626)
queryWrapper.isNull("processing");
} else { } else {
queryWrapper.eq("processing", 0); queryWrapper.eq("processing", processing);
}
} }
// 直接列等值过滤 // 直接列等值过滤
...@@ -148,22 +164,33 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -148,22 +164,33 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
if (param.getName() != null && !param.getName().isEmpty()) { if (param.getName() != null && !param.getName().isEmpty()) {
queryWrapper.like("name", param.getName()); queryWrapper.like("name", param.getName());
} }
// ERP: title 关键词同时搜索 name 和 title 两列(对齐 TS:610-611)
if (param.getTitle() != null && !param.getTitle().isEmpty()) { if (param.getTitle() != null && !param.getTitle().isEmpty()) {
if (isErp) {
queryWrapper.and(w -> w.like("name", param.getTitle()).or().like("title", param.getTitle()));
} else {
queryWrapper.like("title", param.getTitle()); queryWrapper.like("title", param.getTitle());
} }
}
// 工厂过滤(factory_id 是 custom_product_info 的直接列) // 工厂过滤(factory_id 是 custom_product_info 的直接列)
if (param.getFactoryIds() != null) { if (param.getFactoryIds() != null) {
queryWrapper.in("factory_id", param.getFactoryIds()); queryWrapper.in("factory_id", param.getFactoryIds());
} }
// DIY 用户过滤 与 黑名单过滤:需要查询关联表取 productId 列表,两者取交集 // DIY 用户过滤与黑名单过滤(仅标准分页使用,ERP 走 native SQL 权限过滤)
if (!isErp) {
applyDiyAndBlacklistFilter(param, queryWrapper);
}
}
/** DIY 用户与黑名单过滤(标准分页专用) */
private void applyDiyAndBlacklistFilter(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper) {
List<Integer> idListFromDiyUser = null; List<Integer> idListFromDiyUser = null;
List<Integer> idListFromBlacklist = null; List<Integer> idListFromBlacklist = null;
if (param.getDiyUserId() != null) { if (param.getDiyUserId() != null) {
if (param.getDiyUserId() == -1) { if (param.getDiyUserId() == -1) {
// 查询未绑定任何 DIY 用户的产品:先找出已绑定的 productId,再 NOT IN
List<Integer> boundIds = customProductDiyUserRelDomainService.list().stream() List<Integer> boundIds = customProductDiyUserRelDomainService.list().stream()
.map(CustomProductDiyUserRelEntity::getProductId) .map(CustomProductDiyUserRelEntity::getProductId)
.distinct() .distinct()
...@@ -180,7 +207,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -180,7 +207,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.distinct() .distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
if (idListFromDiyUser.isEmpty()) { if (idListFromDiyUser.isEmpty()) {
// 该 DIY 用户未绑定任何商品,直接返回空结果
queryWrapper.eq("id", -1); queryWrapper.eq("id", -1);
} }
} }
...@@ -196,13 +222,12 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -196,13 +222,12 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
// diyUserId 和 blackUserId 同时存在时取交集
if (idListFromDiyUser != null && idListFromBlacklist != null) { if (idListFromDiyUser != null && idListFromBlacklist != null) {
List<Integer> intersection = idListFromDiyUser.stream() List<Integer> intersection = idListFromDiyUser.stream()
.filter(idListFromBlacklist::contains) .filter(idListFromBlacklist::contains)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (intersection.isEmpty()) { if (intersection.isEmpty()) {
queryWrapper.eq("id", -1); // 交集为空,返回空结果 queryWrapper.eq("id", -1);
} else { } else {
queryWrapper.in("id", intersection); queryWrapper.in("id", intersection);
} }
...@@ -211,8 +236,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -211,8 +236,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} else if (idListFromBlacklist != null) { } else if (idListFromBlacklist != null) {
queryWrapper.in("id", idListFromBlacklist); queryWrapper.in("id", idListFromBlacklist);
} }
queryWrapper.orderByDesc("id");
} }
@Override @Override
...@@ -229,6 +252,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -229,6 +252,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
computeProductPricesFromItems(dto); computeProductPricesFromItems(dto);
// 3. 编程式事务写入所有表 // 3. 编程式事务写入所有表
try {
transactionTemplate.executeWithoutResult(status -> { transactionTemplate.executeWithoutResult(status -> {
// 3a. 保存主表 // 3a. 保存主表
CustomProductInfoEntity entity = buildEntityFromSaveDTO(dto, sku); CustomProductInfoEntity entity = buildEntityFromSaveDTO(dto, sku);
...@@ -269,43 +293,67 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -269,43 +293,67 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
// 3l. 事务内记录日志(对齐 TS:日志与数据写入同一事务) // 3l. 事务内记录日志(对齐 TS:日志与数据写入同一事务)
saveLogTransaction(productId, "创建商品"); saveLogTransaction(productId, "创建商品");
}); });
} catch (Exception e) {
log.error("[ saveFull ] 创建商品失败, sku: {}", sku, e);
throw new ServiceException("创建商品失败,sku= " + sku);
}
} }
@Override @Override
public void updateFull(CustomProductInfoUpdateDTO dto) { public void updateFull(CustomProductInfoUpdateSnakeDTO dto) {
/*CustomAsserts.nonNull(dto, "更新参数不能为空"); CustomAsserts.nonNull(dto, "更新参数不能为空");
CustomAsserts.nonNull(dto.getId(), "商品 ID 不能为空"); CustomAsserts.nonNull(dto.getId(), "商品 ID 不能为空");
// 查询旧记录,校验商品存在性
CustomProductInfoEntity oldEntity = customProductInfoDomainService.getById(dto.getId());
if (oldEntity == null) {
throw new ServiceException("商品信息不存在, id=" + dto.getId());
}
// SKU属性类不能新增(对齐 TS:357-359)
if ((oldEntity.getProperty2CateId() == null && dto.getProperty2_cate_id() != null) ||
(oldEntity.getProperty3CateId() == null && dto.getProperty3_cate_id() != null)) {
throw new ServiceException("SKU属性类不能新增");
}
// 将 sizeChange 合并到 imageChange 一起处理
CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO mergedImageChange =
mergeImageChanges(dto.getImageChange(), dto.getSizeChange());
// 编程式事务写入所有表
try {
transactionTemplate.executeWithoutResult(status -> { transactionTemplate.executeWithoutResult(status -> {
Integer productId = dto.getId(); Integer productId = oldEntity.getId();
// 1. 更新主表 // 1. 更新主表(显式保留原 create_time)
CustomProductInfoEntity entity = buildEntityFromSaveDTO(dto, null); CustomProductInfoEntity entity = buildEntityFromSaveDTO(dto, null);
entity.setId(productId); entity.setId(productId);
entity.setCreateTime(oldEntity.getCreateTime());
customProductInfoDomainService.updateById(entity); customProductInfoDomainService.updateById(entity);
// 2. 处理子项的增/删/改 // 2. 处理子项的增/删/改,返回新增子项的 SKU→Entity 映射
Map<String, CustomProductItemEntity> newItemMap = null;
if (dto.getProductChange() != null) { if (dto.getProductChange() != null) {
handleItemChanges(dto.getProductChange(), productId); newItemMap = handleItemChanges(dto.getProductChange(), productId, dto);
} }
// 3. 处理工厂价格变更 // 2b. 批量同步所有子项的 product_no 和 print_type
if (dto.getProductFactoryPriceChange() != null) { syncItemsProductNoAndPrintType(productId, dto.getProduct_no(), dto.getPrint_type());
handleFactoryPriceChanges(dto.getProductFactoryPriceChange(), productId);
// 3. 处理工厂价格变更(传入 newItemMap 用于回填 item_id)
if (dto.getFactoryPriceChange() != null) {
handleFactoryPriceChanges(dto.getFactoryPriceChange(), productId, newItemMap);
} }
// 4. 销毁并重建属性 // 4. 销毁并重建属性
rebuildProperties(dto.getSkuProperties(), dto.getNormalProperties(), productId); rebuildProperties(dto.getSkuProperties(), dto.getNormalProperties(), productId);
// 5. 处理图片变更 // 5. 处理图片变更(统一处理合并后的 imageChange)
if (dto.getProductImageChange() != null) { if (mergedImageChange != null) {
handleImageChanges(dto.getProductImageChange(), productId, IMAGE_TYPE_NORMAL); handleImageChanges(mergedImageChange, productId);
}
if (dto.getProductSizeChange() != null) {
handleImageChanges(dto.getProductSizeChange(), productId, IMAGE_TYPE_SIZE);
} }
// 6. 更新备注(先删后插) // 6. 更新备注
updateRemarks(dto.getRemark(), dto.getCnRemark(), productId); updateRemarks(dto.getRemark(), dto.getCnRemark(), productId);
// 7. 重建工厂/仓库/工艺/DIY用户/价格区间关联(先删后插) // 7. 重建工厂/仓库/工艺/DIY用户/价格区间关联(先删后插)
...@@ -314,7 +362,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -314,7 +362,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
// 8. 事务内记日志 // 8. 事务内记日志
saveLogTransaction(productId, "修改商品信息"); saveLogTransaction(productId, "修改商品信息");
});*/ });
} catch (Exception e) {
log.error("[ updateFull ] 修改商品信息失败, productId: {}", dto.getId(), e);
throw new ServiceException("修改商品信息失败, productId=" + dto.getId());
}
} }
@Override @Override
...@@ -449,7 +501,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -449,7 +501,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 通过 AdminPropertyService 解析属性名称,填充到 FullVO 的 skuProperties / normalProperties 中 * 通过 AdminPropertyService 解析属性名称,填充到 FullVO 的 skuProperties / normalProperties 中
* <p> * <p>
* 对齐 TS:217-236 — 将数据库中存储的 property_id/value_id 转换为带名称的属性列表。 * 对齐ts代码,将数据库中存储的 property_id/value_id 转换为带名称的属性列表。
* *
* @param properties 数据库原始属性记录 * @param properties 数据库原始属性记录
* @param fullVO 待填充的商品完整详情 * @param fullVO 待填充的商品完整详情
...@@ -469,7 +521,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -469,7 +521,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
// 调用 saas admin API 获取属性定义(含 valueList) // 调用 saas admin API 获取属性定义(含 valueList)
List<BasePropertyModel> publicProperties = saasAdminService.getPropertyByIds(propIds); List<BasePropertyModel> publicProperties = saasAdminService.getPropertyByIds(propIds);
if (publicProperties == null || publicProperties.isEmpty()) { if (CollectionUtils.isEmpty(publicProperties)) {
return; return;
} }
// 收集当前商品使用的 value_id // 收集当前商品使用的 value_id
...@@ -503,6 +555,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -503,6 +555,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
CustomAsserts.nonNull(dto, "绑定参数不能为空"); CustomAsserts.nonNull(dto, "绑定参数不能为空");
CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空"); CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空");
try {
transactionTemplate.executeWithoutResult(status -> { transactionTemplate.executeWithoutResult(status -> {
for (Integer productId : dto.getProductIds()) { for (Integer productId : dto.getProductIds()) {
// 先删除该商品的所有现有绑定 // 先删除该商品的所有现有绑定
...@@ -525,6 +578,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -525,6 +578,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
saveLogBatch("绑定客户", dto.getProductIds()); saveLogBatch("绑定客户", dto.getProductIds());
}); });
} catch (Exception e) {
log.error("[ bindsDiyUser ] 绑定客户失败, productIds: {}", dto.getProductIds(), e);
throw new ServiceException("绑定客户失败: " + e.getMessage())
.setDetailMessage(e.toString());
}
} }
@Override @Override
...@@ -532,6 +590,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -532,6 +590,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
CustomAsserts.nonNull(dto, "黑名单参数不能为空"); CustomAsserts.nonNull(dto, "黑名单参数不能为空");
CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空"); CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空");
try {
transactionTemplate.executeWithoutResult(status -> { transactionTemplate.executeWithoutResult(status -> {
for (Integer productId : dto.getProductIds()) { for (Integer productId : dto.getProductIds()) {
// 先删除该商品的所有现有黑名单 // 先删除该商品的所有现有黑名单
...@@ -554,6 +613,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -554,6 +613,11 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
saveLogBatch("加入黑名单", dto.getProductIds()); saveLogBatch("加入黑名单", dto.getProductIds());
}); });
} catch (Exception e) {
log.error("[ addBlackList ] 加入黑名单失败, productIds: {}", dto.getProductIds(), e);
throw new ServiceException("加入黑名单失败: " + e.getMessage())
.setDetailMessage(e.toString());
}
} }
@Override @Override
...@@ -621,7 +685,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -621,7 +685,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
@Override @Override
public List<DbDiyVO> getErpBindsDiyById(Integer id, String userMark, String namespace) { public List<DbDiyVO> getErpBindsDiyById(Integer id, String userMark, String namespace) {
CustomAsserts.nonNull(id, "商品 ID 不能为空"); CustomAsserts.nonNull(id, "商品 ID 不能为空");
// 1. 根据 userMark 或 namespace 查询用户(对齐 TS:751-759) // 1. 根据 userMark 或 namespace 查询用户
DbDiyUserEntity user; DbDiyUserEntity user;
if (StringUtils.isNotBlank(userMark)) { if (StringUtils.isNotBlank(userMark)) {
user = diyUserService.getByUserMark(userMark); user = diyUserService.getByUserMark(userMark);
...@@ -631,57 +695,91 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -631,57 +695,91 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
if (user == null) { if (user == null) {
throw new ServiceException("客户不存在"); throw new ServiceException("客户不存在");
} }
// 2. 查询 product_template_info 获取 diy_id 列表(对齐 TS:760-762) // 2. 查询 product_template_info 获取 diy_id 列表
List<ProductTemplateInfoEntity> templates = productTemplateInfoDomainService.selectByProductId(id); List<ProductTemplateInfoEntity> templates = productTemplateInfoDomainService.selectByProductId(id);
if (templates.isEmpty()) { if (CollectionUtils.isEmpty(templates)) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<Integer> diyIds = templates.stream() List<Integer> diyIds = templates.stream().map(ProductTemplateInfoEntity::getDiyId)
.map(ProductTemplateInfoEntity::getDiyId) .filter(Objects::nonNull).distinct().collect(Collectors.toList());
.filter(d -> d != null) // 3. 查询 db_diy,根据用户类型过滤状态
.distinct()
.collect(Collectors.toList());
// 3. 查询 db_diy,根据用户类型过滤状态(对齐 TS:766-783)
List<Integer> statusList; List<Integer> statusList;
String userName = user.getName(); String userName = user.getName();
if ("demo".equals(userName)) { if ("demo".equals(userName)) {
statusList = Arrays.asList(TemplateStatus.SHELF_CODE, statusList = Arrays.asList(TemplateStatus.SHELF_CODE, TemplateStatus.WAIT_SHELF.getCode());
TemplateStatus.WAIT_SHELF.getCode()); // demo 用户可见待上架
} else { } else {
statusList = Collections.singletonList(TemplateStatus.SHELF_CODE); statusList = Collections.singletonList(TemplateStatus.SHELF_CODE);
} }
List<DbDiyEntity> diys = dbDiyDomainService.list( List<DbDiyEntity> diys = dbDiyDomainService.list(new LambdaQueryWrapper<DbDiyEntity>()
new LambdaQueryWrapper<DbDiyEntity>() .in(DbDiyEntity::getId, diyIds).in(DbDiyEntity::getStatus, statusList));
.in(DbDiyEntity::getId, diyIds) if (CollectionUtils.isEmpty(diys)) {
.in(DbDiyEntity::getStatus, statusList)); return Collections.emptyList();
if (diys.isEmpty()) { }
// 4. 权限过滤:user_ids / ban_user_ids
final Integer userId = user.getId();
diys = diys.stream().filter(d -> isUserAuthorized(d.getUserIds(), userId))
.filter(d -> !isUserBanned(d.getBanUserIds(), userId)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(diys)) {
return Collections.emptyList(); return Collections.emptyList();
} }
// 4. 转换为 VO 并附带效果图(对齐 TS:783 include DbDiyXiaoguotu) // 5. 批量查询所有效果图,按 diyId 分组(1 次 IN 查询替代 N 次逐条查询)
List<Integer> ids = diys.stream().map(DbDiyEntity::getId).collect(Collectors.toList());
Map<Integer, List<DbDiyXiaoguotuEntity>> xiaoguotuMap = dbDiyXiaoguotuDomainService.selectByDiyIds(ids)
.stream().collect(Collectors.groupingBy(DbDiyXiaoguotuEntity::getDiyId));
// 6. 转换为 VO 并附带效果图
return diys.stream().map(diy -> { return diys.stream().map(diy -> {
DbDiyVO vo = BeanMapper.mapper().convert(diy, DbDiyVO.class); DbDiyVO vo = BeanMapper.mapper().convert(diy, DbDiyVO.class);
List<DbDiyXiaoguotuEntity> xiaoguotus = dbDiyXiaoguotuDomainService.selectByDiyId(diy.getId()); List<DbDiyXiaoguotuEntity> xiaoguotus = xiaoguotuMap.getOrDefault(diy.getId(), Collections.emptyList());
// 将效果图数据附加到 VO(通过扩展方式,当前 DbDiyVO 无 xiaoguotu 字段, if (CollectionUtils.isNotEmpty(xiaoguotus)) {
// 如有需要可扩展 DbDiyVO) vo.setXiaoguotuList(xiaoguotus.stream()
.map(e -> BeanMapper.mapper().convert(e, DbDiyXiaoguotuVO.class)).collect(Collectors.toList()));
}
return vo; return vo;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
/**
* 检查用户是否在授权名单中(对齐 TS:774-777 FIND_IN_SET)
* <p>
* user_ids 为 null 或空字符串 = 对所有人开放。
*/
private boolean isUserAuthorized(String userIds, Integer userId) {
if (StringUtils.isBlank(userIds)) {
return true;
}
return Arrays.asList(userIds.split(",")).contains(String.valueOf(userId));
}
/**
* 检查用户是否在黑名单中(对齐 TS:778-781 FIND_IN_SET)
* <p>
* ban_user_ids 为 null 或空字符串 = 无人被禁止。
*/
private boolean isUserBanned(String banUserIds, Integer userId) {
if (StringUtils.isBlank(banUserIds)) {
return false;
}
return Arrays.asList(banUserIds.split(",")).contains(String.valueOf(userId));
}
@Override @Override
public IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param) { public IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param) {
CustomAsserts.nonNull(param, "分页查询参数不能为空"); CustomAsserts.nonNull(param, "分页查询参数不能为空");
// 1. 查询用户(对齐 TS:683-684) // 1. 查询用户(对齐 TS:683-684)
DbDiyUserEntity user = null; DbDiyUserEntity user = null;
if (StringUtils.isNotBlank(param.getUserMark())) { if (StringUtils.isNotBlank(param.getUserMark())) {
user = diyUserService.getByUserMark(param.getUserMark()); user = diyUserService.getByUserMark(param.getUserMark());
if (user == null) { if (user == null) {
// 用户不存在,返回空结果 return emptyPage(param);
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize());
} }
} }
// 2. 构建查询条件(复用 + ERP 特有逻辑)
// 2. 构建查询条件(ERP 模式:title OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单)
QueryWrapper<CustomProductInfoEntity> queryWrapper = new QueryWrapper<>(); QueryWrapper<CustomProductInfoEntity> queryWrapper = new QueryWrapper<>();
toQueryWrapper(param, queryWrapper); toQueryWrapper(param, queryWrapper, true);
// 3. DIY 模板过滤(对齐 TS:630-657) // 3. DIY 模板过滤(对齐 TS:630-657)
if (StringUtils.isNotBlank(param.getDiySku()) || StringUtils.isNotBlank(param.getSource()) if (StringUtils.isNotBlank(param.getDiySku()) || StringUtils.isNotBlank(param.getSource())
|| param.getDiyId() != null) { || param.getDiyId() != null) {
...@@ -697,55 +795,82 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -697,55 +795,82 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
List<DbDiyEntity> diys = dbDiyDomainService.list(diyWrapper); List<DbDiyEntity> diys = dbDiyDomainService.list(diyWrapper);
if (diys.isEmpty()) { if (diys.isEmpty()) {
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize()); return emptyPage(param);
} }
List<Integer> diyIds = diys.stream().map(DbDiyEntity::getId).collect(Collectors.toList()); List<Integer> diyIds = diys.stream().map(DbDiyEntity::getId).collect(Collectors.toList());
List<ProductTemplateInfoEntity> temps = productTemplateInfoDomainService.selectByDiyIds(diyIds); List<ProductTemplateInfoEntity> temps = productTemplateInfoDomainService.selectByDiyIds(diyIds);
if (temps.isEmpty()) { if (temps.isEmpty()) {
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize()); return emptyPage(param);
} }
List<Integer> productIds = temps.stream() List<Integer> productIds = temps.stream()
.map(ProductTemplateInfoEntity::getProductId) .map(ProductTemplateInfoEntity::getProductId)
.filter(pid -> pid != null) .filter(Objects::nonNull)
.distinct() .distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
if (productIds.isEmpty()) { if (productIds.isEmpty()) {
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize()); return emptyPage(param);
} }
queryWrapper.in("id", productIds); queryWrapper.in("id", productIds);
} }
// 4. 仓库国家过滤(对齐 TS:673-681) // 4. 仓库国家过滤(对齐 TS:673-681)
if (StringUtils.isNotBlank(param.getWarehouseCountry())) { if (StringUtils.isNotBlank(param.getWarehouseCountry())) {
List<CustomWarehouseInfoEntity> warehouses = customWarehouseInfoDomainService.list( List<CustomWarehouseInfoEntity> warehouses = customWarehouseInfoDomainService.list(
new LambdaQueryWrapper<CustomWarehouseInfoEntity>() new LambdaQueryWrapper<CustomWarehouseInfoEntity>()
.eq(CustomWarehouseInfoEntity::getCountryCode, param.getWarehouseCountry())); .eq(CustomWarehouseInfoEntity::getCountryCode, param.getWarehouseCountry()));
if (warehouses.isEmpty()) { if (warehouses.isEmpty()) {
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize()); return emptyPage(param);
} }
List<Integer> wIds = warehouses.stream() List<Integer> wIds = warehouses.stream()
.map(w -> w.getId().intValue()) .map(w -> w.getId().intValue())
.collect(Collectors.toList()); .collect(Collectors.toList());
List<CustomProductWarehouseRelEntity> rels = customProductWarehouseRelDomainService.selectByWarehouseIds(wIds); List<CustomProductWarehouseRelEntity> rels = customProductWarehouseRelDomainService.selectByWarehouseIds(wIds);
if (rels.isEmpty()) { if (rels.isEmpty()) {
return new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize()); return emptyPage(param);
} }
List<Integer> productIds = rels.stream() List<Integer> productIds = rels.stream()
.map(CustomProductWarehouseRelEntity::getProductId).distinct().collect(Collectors.toList()); .map(CustomProductWarehouseRelEntity::getProductId).distinct().collect(Collectors.toList());
queryWrapper.in("id", productIds); queryWrapper.in("id", productIds);
} }
// 5. 执行分页查询
queryWrapper.orderByDesc("id"); // 5. ERP 权限过滤(对齐 TS:686-706 — native SQL 黑名单排除 + 用户绑定过滤)
// 使用单次 JOIN 查询,避免 Java 层多次查询+set 操作,保证性能
if (user != null) {
List<Integer> allowedIds = customProductInfoDomainService.selectIdsByErpPermission(user.getId());
if (allowedIds.isEmpty()) {
return emptyPage(param);
}
// MyBatis-Plus 多个 in("id", ...) 叠加 = SQL 层 AND 交集
queryWrapper.in("id", allowedIds);
}
// 6. 排序(对齐 TS:716-721:sort IS NULL ASC, sort ASC, id DESC)
// MySQL 默认 ASC 时空值排最前,等价于 sort IS NULL ASC
queryWrapper.orderByAsc("sort").orderByDesc("id");
// 7. 执行分页查询
IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param); IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param);
// 6. 转换并应用外部定价(对齐 TS:731-738)
// 预计算折扣率(放入 final 变量,供 lambda 使用) // 8. 批量查询 DIY 上架状态(对齐 TS:723-738,单次 IN 查询避免 N+1)
List<CustomProductInfoEntity> rows = page.getRecords();
final Map<Integer, Boolean> diyShelfStatusMap;
if (!rows.isEmpty()) {
diyShelfStatusMap = batchQueryDiyShelfStatus(rows);
} else {
diyShelfStatusMap = Collections.emptyMap();
}
// 9. 预计算折扣率(对齐 TS:731-733 setProductExternalPrice)
final BigDecimal discountRate; final BigDecimal discountRate;
if (user != null && user.getDiscount() != null) { if (user != null && user.getDiscount() != null) {
discountRate = user.getDiscount().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP); discountRate = user.getDiscount().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
} else { } else {
discountRate = null; discountRate = null;
} }
return page.convert(e -> { return page.convert(e -> {
CustomProductInfoVO vo = BeanMapper.mapper().convert(e, CustomProductInfoVO.class); CustomProductInfoVO vo = BeanMapper.mapper().convert(e, CustomProductInfoVO.class);
// 应用用户折扣定价
if (discountRate != null) { if (discountRate != null) {
if (vo.getSalesPrice() != null) { if (vo.getSalesPrice() != null) {
vo.setSalesPrice(vo.getSalesPrice().multiply(discountRate).setScale(2, RoundingMode.HALF_UP)); vo.setSalesPrice(vo.getSalesPrice().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
...@@ -754,10 +879,40 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -754,10 +879,40 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
vo.setSalesPriceMax(vo.getSalesPriceMax().multiply(discountRate).setScale(2, RoundingMode.HALF_UP)); vo.setSalesPriceMax(vo.getSalesPriceMax().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
} }
} }
// 标记绑定的 DIY 模板是否已上架
vo.setDiyShelfStatus(diyShelfStatusMap.getOrDefault(e.getDiyId(), false));
return vo; return vo;
}); });
} }
/** 返回空分页结果 */
private IPage<CustomProductInfoVO> emptyPage(CustomProductInfoSnakeDTO param) {
return new Page<>(param.getCurrent(), param.getSize());
}
/**
* 批量查询 DIY 模板上架状态,返回 diyId → isShelf 映射
* <p>
* 对齐 TS:724-731,使用单次 IN 查询代替 N+1 逐条查询。
*/
private Map<Integer, Boolean> batchQueryDiyShelfStatus(List<CustomProductInfoEntity> rows) {
List<Integer> diyIds = rows.stream()
.map(CustomProductInfoEntity::getDiyId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (diyIds.isEmpty()) {
return Collections.emptyMap();
}
List<DbDiyEntity> diys = dbDiyDomainService.list(
new LambdaQueryWrapper<DbDiyEntity>()
.select(DbDiyEntity::getId, DbDiyEntity::getStatus)
.in(DbDiyEntity::getId, diyIds));
return diys.stream().collect(Collectors.toMap(
DbDiyEntity::getId,
d -> d.getStatus() != null && d.getStatus().equals(TemplateStatus.SHELF_CODE)));
}
/** /**
* 从事务外的子项列表中计算主表的 factory_price / sales_price / sales_price_max * 从事务外的子项列表中计算主表的 factory_price / sales_price / sales_price_max
* <p> * <p>
...@@ -857,18 +1012,18 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -857,18 +1012,18 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* <p> * <p>
* 对齐 TS {@code save} 方法 132-139 行。 * 对齐 TS {@code save} 方法 132-139 行。
*/ */
private void saveFactoryPriceRels(List<FactoryPriceRelDTO> factoryPriceList, private void saveFactoryPriceRels(List<FactoryPriceRelSnakeDTO> factoryPriceList,
Integer productId, Integer productId,
Map<String, CustomProductItemEntity> itemMap) { Map<String, CustomProductItemEntity> itemMap) {
if (factoryPriceList == null || factoryPriceList.isEmpty()) { if (CollectionUtils.isEmpty(factoryPriceList)) {
return; return;
} }
List<CustomProductFactoryPriceRelEntity> rels = new ArrayList<>(); List<CustomProductFactoryPriceRelEntity> rels = new ArrayList<>();
for (FactoryPriceRelDTO dto : factoryPriceList) { for (FactoryPriceRelSnakeDTO dto : factoryPriceList) {
CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class); CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class);
rel.setProductId(productId); rel.setProductId(productId);
// 从 itemMap 中查找对应的子项,替换 item_sku 为生成的 SKU 并填充 item_id(对齐 TS:134-137) // 从 itemMap 中查找对应的子项,替换 item_sku 为生成的 SKU 并填充 item_id(对齐 TS:134-137)
CustomProductItemEntity item = itemMap.get(dto.getItemSku()); CustomProductItemEntity item = itemMap.get(dto.getItem_sku());
if (item != null) { if (item != null) {
rel.setItemSku(item.getSku()); // 替换为替换后的 SKU rel.setItemSku(item.getSku()); // 替换为替换后的 SKU
rel.setItemId(item.getId()); // 填充自增 ID rel.setItemId(item.getId()); // 填充自增 ID
...@@ -931,12 +1086,12 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -931,12 +1086,12 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 保存价格区间关联 * 保存价格区间关联
*/ */
private void saveFactoryPriceIntervalRels(List<FactoryPriceIntervalRelDTO> intervals, Integer productId) { private void saveFactoryPriceIntervalRels(List<FactoryPriceIntervalRelSnakeDTO> intervals, Integer productId) {
if (intervals == null || intervals.isEmpty()) { if (intervals == null || intervals.isEmpty()) {
return; return;
} }
List<CustomProductFactoryPriceIntervalRelEntity> rels = intervals.stream().map(dto -> { List<CustomProductFactoryPriceIntervalRelEntity> rels = intervals.stream().map(dto -> {
CustomProductFactoryPriceIntervalRelEntity rel = BeanMapper.mapper().convert(dto, CustomProductFactoryPriceIntervalRelEntity.class); CustomProductFactoryPriceIntervalRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceIntervalRelEntity.class);
rel.setProductId(productId); rel.setProductId(productId);
return rel; return rel;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
...@@ -951,7 +1106,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -951,7 +1106,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
CustomProductImageEntity entity = BeanMapper.snakeCase().convert(dto, CustomProductImageEntity.class); CustomProductImageEntity entity = BeanMapper.snakeCase().convert(dto, CustomProductImageEntity.class);
entity.setProductId(productId); entity.setProductId(productId);
entity.setType(type); entity.setType(type);
entity.setCreateTime(new Date());
return entity; return entity;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
...@@ -980,7 +1134,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -980,7 +1134,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
r.setRemark(remark); r.setRemark(remark);
customProductRemarkDomainService.save(r); customProductRemarkDomainService.save(r);
} }
if (StringUtils.isNotBlank(remark)) { if (StringUtils.isNotBlank(cnRemark)) {
CustomProductCnRemarkEntity cr = new CustomProductCnRemarkEntity(); CustomProductCnRemarkEntity cr = new CustomProductCnRemarkEntity();
cr.setProductId(productId); cr.setProductId(productId);
cr.setRemark(cnRemark); cr.setRemark(cnRemark);
...@@ -1068,55 +1222,154 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1068,55 +1222,154 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 处理子项变更(增/删/改) * 处理子项变更(增/删/改)
* <p>
* 对齐 TS update:407-420 — 新增子项逐条保存以获取自增 ID,
* 返回 SKU→Entity 映射供后续工厂价格 item_id 回填。
*
* @return 新增子项的原始 SKU → 已保存实体映射(含自增 ID)
*/ */
private void handleItemChanges(com.jomalls.custom.app.dto.ProductChangeDTO change, Integer productId) { private Map<String, CustomProductItemEntity> handleItemChanges(
ProductChangeSnakeDTO change, Integer productId, CustomProductInfoSnakeDTO dto) {
Map<String, CustomProductItemEntity> newItemMap = new HashMap<>();
if (change == null) { if (change == null) {
return; return newItemMap;
} }
// 删除 // 删除
if (change.getRemoveList() != null && !change.getRemoveList().isEmpty()) { if (CollectionUtils.isNotEmpty(change.getRemoveList())) {
customProductItemDomainService.removeByIds(change.getRemoveList()); customProductItemDomainService.removeByIds(change.getRemoveList());
} }
// 修改 // 修改
if (change.getUpdateList() != null) { if (CollectionUtils.isNotEmpty(change.getUpdateList())) {
for (var itemDTO : change.getUpdateList()) { for (CustomProductItemSnakeDTO itemDTO : change.getUpdateList()) {
CustomProductItemEntity item = BeanMapper.mapper().convert(itemDTO, CustomProductItemEntity.class); CustomProductItemEntity item = BeanMapper.snakeCase().convert(itemDTO, CustomProductItemEntity.class);
item.setProductId(productId); item.setProductId(productId);
customProductItemDomainService.updateById(item); customProductItemDomainService.updateById(item);
} }
} }
// 新增 // 新增(逐条保存以获取自增 ID,构建 SKU→Entity 映射)
if (change.getAddList() != null) { if (CollectionUtils.isNotEmpty(change.getAddList())) {
for (var itemDTO : change.getAddList()) { for (CustomProductItemSnakeDTO itemDTO : change.getAddList()) {
CustomProductItemEntity item = BeanMapper.mapper().convert(itemDTO, CustomProductItemEntity.class); CustomProductItemEntity item = BeanMapper.snakeCase().convert(itemDTO, CustomProductItemEntity.class);
item.setProductId(productId); item.setProductId(productId);
// 同步主表的 print_type 和 product_no 到新子项
item.setPrintType(dto.getPrint_type());
item.setProductNo(dto.getProduct_no());
customProductItemDomainService.save(item); customProductItemDomainService.save(item);
newItemMap.put(itemDTO.getSku(), item);
}
}
return newItemMap;
}
/**
* 批量同步所有子项的 product_no 和 print_type
* <p>
* 当主表的 product_no 或 print_type 变更时,统一更新该商品下所有子项的对应字段。
*/
private void syncItemsProductNoAndPrintType(Integer productId, String productNo, Integer printType) {
LambdaUpdateWrapper<CustomProductItemEntity> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CustomProductItemEntity::getProductId, productId);
if (productNo != null) {
wrapper.set(CustomProductItemEntity::getProductNo, productNo);
} }
if (printType != null) {
wrapper.set(CustomProductItemEntity::getPrintType, printType);
}
customProductItemDomainService.update(wrapper);
}
/**
* 将 sizeChange 合并到 imageChange(对齐 TS:369-379)
* <p>
* sizeChange 的 addList/updateList 设置 type=1 后合并到 imageChange,
* removeList 也合并,最终统一处理。
*/
private CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO mergeImageChanges(
CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO imageChange,
CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO sizeChange) {
List<CustomProductImageSnakeDTO> addList = new ArrayList<>();
List<CustomProductImageSnakeDTO> updateList = new ArrayList<>();
List<Integer> removeList = new ArrayList<>();
// 按类型收集,对齐
collectFromSource(imageChange, IMAGE_TYPE_NORMAL, addList, updateList, removeList);
collectFromSource(sizeChange, IMAGE_TYPE_SIZE, addList, updateList, removeList);
// 无任何变更数据时直接返回 null
if (CollectionUtils.isEmpty(addList) && CollectionUtils.isEmpty(updateList) && CollectionUtils.isEmpty(removeList)) {
return null;
}
CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO merged = new CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO();
merged.setAddList(CollectionUtils.isNotEmpty(addList) ? addList : null);
merged.setUpdateList(CollectionUtils.isNotEmpty(updateList) ? updateList : null);
merged.setRemoveList(CollectionUtils.isNotEmpty(removeList) ? removeList : null);
return merged;
}
/**
* 将 source 中的图片变更按指定 type 写入目标集合
* <p>
* 副作用:会直接修改 source 中各元素的 type 字段(与 TS 行为一致)。
*/
private void collectFromSource(
CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO source, int type,
List<CustomProductImageSnakeDTO> addTarget,
List<CustomProductImageSnakeDTO> updateTarget,
List<Integer> removeTarget) {
if (source == null) {
return;
}
if (CollectionUtils.isNotEmpty(source.getAddList())) {
source.getAddList().forEach(item -> item.setType(type));
addTarget.addAll(source.getAddList());
}
if (CollectionUtils.isNotEmpty(source.getUpdateList())) {
source.getUpdateList().forEach(item -> item.setType(type));
updateTarget.addAll(source.getUpdateList());
}
if (CollectionUtils.isNotEmpty(source.getRemoveList())) {
removeTarget.addAll(source.getRemoveList());
} }
} }
/** /**
* 处理工厂价格变更(增/删/改) * 处理工厂价格变更(增/删/改)
* <p>
* 对齐 TS update:422-430 — 顺序:新增 → 修改 → 删除。
* 新增时从 newItemMap 回填 item_id(对齐 TS:413-416)。
*/ */
private void handleFactoryPriceChanges( private void handleFactoryPriceChanges(
CustomProductInfoUpdateDTO.ProductFactoryPriceChangeDTO change, Integer productId) { CustomProductInfoUpdateSnakeDTO.ProductFactoryPriceChangeDTO change, Integer productId,
if (change.getRemoveList() != null && !change.getRemoveList().isEmpty()) { Map<String, CustomProductItemEntity> newItemMap) {
customProductFactoryPriceRelDomainService.removeByIds(change.getRemoveList()); // 新增(对齐 TS 先新增)
} if (CollectionUtils.isNotEmpty(change.getAddList())) {
if (change.getUpdateList() != null) { for (FactoryPriceRelSnakeDTO dto : change.getAddList()) {
for (var dto : change.getUpdateList()) { CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class);
CustomProductFactoryPriceRelEntity rel = BeanMapper.mapper().convert(dto, CustomProductFactoryPriceRelEntity.class);
rel.setProductId(productId); rel.setProductId(productId);
customProductFactoryPriceRelDomainService.updateById(rel); // 从新增子项映射中回填 item_id 和 item_sku(对齐 TS:413-416)
if (newItemMap != null && StringUtils.isNotBlank(dto.getItem_sku())) {
CustomProductItemEntity newItem = newItemMap.get(dto.getItem_sku());
if (newItem != null) {
rel.setItemId(newItem.getId());
rel.setItemSku(newItem.getSku());
} }
} }
if (change.getAddList() != null) {
for (var dto : change.getAddList()) {
CustomProductFactoryPriceRelEntity rel = BeanMapper.mapper().convert(dto, CustomProductFactoryPriceRelEntity.class);
rel.setProductId(productId);
customProductFactoryPriceRelDomainService.save(rel); customProductFactoryPriceRelDomainService.save(rel);
} }
} }
// 修改
if (CollectionUtils.isNotEmpty(change.getUpdateList())) {
for (FactoryPriceRelSnakeDTO dto : change.getUpdateList()) {
CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class);
rel.setProductId(productId);
customProductFactoryPriceRelDomainService.updateById(rel);
}
}
// 删除
if (CollectionUtils.isNotEmpty(change.getRemoveList())) {
customProductFactoryPriceRelDomainService.removeByIds(change.getRemoveList());
}
} }
/** /**
...@@ -1134,55 +1387,84 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1134,55 +1387,84 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
/** /**
* 处理图片变更 * 处理图片变更(增/删/改)
* <p>
* 图片的 type 已在 mergeImageChanges 中预设,此处不再注入。
*/ */
private void handleImageChanges( private void handleImageChanges(CustomProductInfoUpdateSnakeDTO.ProductImageChangeDTO change, Integer productId) {
CustomProductInfoUpdateDTO.ProductImageChangeDTO change, Integer productId, Integer type) { if (change == null) {
if (change.getRemoveList() != null && !change.getRemoveList().isEmpty()) { return;
}
// 删除
if (CollectionUtils.isNotEmpty(change.getRemoveList())) {
customProductImageDomainService.removeByIds(change.getRemoveList()); customProductImageDomainService.removeByIds(change.getRemoveList());
} }
if (change.getAddList() != null) { // 修改
for (var dto : change.getAddList()) { if (CollectionUtils.isNotEmpty(change.getUpdateList())) {
CustomProductImageEntity img = BeanMapper.mapper().convert(dto, CustomProductImageEntity.class); for (CustomProductImageSnakeDTO dto : change.getUpdateList()) {
CustomProductImageEntity img = BeanMapper.snakeCase().convert(dto, CustomProductImageEntity.class);
img.setProductId(productId);
customProductImageDomainService.updateById(img);
}
}
// 新增
if (CollectionUtils.isNotEmpty(change.getAddList())) {
for (CustomProductImageSnakeDTO dto : change.getAddList()) {
CustomProductImageEntity img = BeanMapper.snakeCase().convert(dto, CustomProductImageEntity.class);
img.setProductId(productId); img.setProductId(productId);
img.setType(type);
customProductImageDomainService.save(img); customProductImageDomainService.save(img);
} }
} }
} }
/** /**
* 更新备注(先删后插) * 更新备注
* @param remark : 英文备注
* @param cnRemark : 中文备注
* @param productId : 产品 ID
*/ */
private void updateRemarks(String remark, String cnRemark, Integer productId) { private void updateRemarks(String remark, String cnRemark, Integer productId) {
// 英文备注
customProductRemarkDomainService.remove( // 英文备注,不为空
new LambdaQueryWrapper<CustomProductRemarkEntity>() if (StringUtils.isNotBlank(remark)) {
.eq(CustomProductRemarkEntity::getProductId, productId)); // productId是唯一索引,先查询,如果有则更新,没有则插入
if (remark != null && !remark.isEmpty()) { LambdaQueryWrapper<CustomProductRemarkEntity> enWrapper = new LambdaQueryWrapper<CustomProductRemarkEntity>()
.eq(CustomProductRemarkEntity::getProductId, productId);
CustomProductRemarkEntity enEntity = customProductRemarkDomainService.getOne(enWrapper);
CustomProductRemarkEntity r = new CustomProductRemarkEntity(); CustomProductRemarkEntity r = new CustomProductRemarkEntity();
r.setProductId(productId); r.setProductId(productId);
r.setRemark(remark); r.setRemark(remark);
if (enEntity != null) {
r.setId(enEntity.getId());
customProductRemarkDomainService.updateById(r);
} else {
customProductRemarkDomainService.save(r); customProductRemarkDomainService.save(r);
} }
// 中文备注 }
customProductCnRemarkDomainService.remove( // 中文备注,不为空
new LambdaQueryWrapper<CustomProductCnRemarkEntity>() if (StringUtils.isNotBlank(cnRemark)) {
.eq(CustomProductCnRemarkEntity::getProductId, productId)); // productId是唯一索引,先查询,如果有则更新,没有则插入
if (cnRemark != null && !cnRemark.isEmpty()) { LambdaQueryWrapper<CustomProductCnRemarkEntity> cnWrapper = new LambdaQueryWrapper<CustomProductCnRemarkEntity>()
.eq(CustomProductCnRemarkEntity::getProductId, productId);
CustomProductCnRemarkEntity cnEntity = customProductCnRemarkDomainService.getOne(cnWrapper);
CustomProductCnRemarkEntity cr = new CustomProductCnRemarkEntity(); CustomProductCnRemarkEntity cr = new CustomProductCnRemarkEntity();
cr.setProductId(productId); cr.setProductId(productId);
cr.setRemark(cnRemark); cr.setRemark(cnRemark);
if (cnEntity != null) {
cr.setId(cnEntity.getId());
customProductCnRemarkDomainService.updateById(cr);
} else {
customProductCnRemarkDomainService.save(cr); customProductCnRemarkDomainService.save(cr);
} }
} }
}
/** /**
* 重建关联关系(先删后插) * 重建关联关系(先删后插)
*/ */
private void rebuildRels(List<Integer> factoryIds, List<Integer> warehouseIds, private void rebuildRels(List<Integer> factoryIds, List<Integer> warehouseIds,
List<Integer> craftIds, List<Integer> diyUserIds, List<Integer> craftIds, List<Integer> diyUserIds,
List<FactoryPriceIntervalRelDTO> intervals, Integer productId) { List<FactoryPriceIntervalRelSnakeDTO> intervals, Integer productId) {
// 产品-工厂关联 // 产品-工厂关联
productFactoryRelDomainService.remove( productFactoryRelDomainService.remove(
new LambdaQueryWrapper<ProductFactoryRelEntity>() new LambdaQueryWrapper<ProductFactoryRelEntity>()
...@@ -1214,17 +1496,16 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1214,17 +1496,16 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
saveFactoryPriceIntervalRels(intervals, productId); saveFactoryPriceIntervalRels(intervals, productId);
} }
// -------- 日志辅助方法 --------
/** /**
* 事务内记录日志 * 事务内记录日志
*/ */
private void saveLogTransaction(Integer productId, String description) { private void saveLogTransaction(Integer productId, String description) {
LoginUser loginUser = SecurityUtils.getLoginUser();
LogCustomProductEntity logEntry = new LogCustomProductEntity(); LogCustomProductEntity logEntry = new LogCustomProductEntity();
logEntry.setProductId(productId); logEntry.setProductId(productId);
logEntry.setDescription(description); logEntry.setDescription(description);
logEntry.setEmployeeId(-1); logEntry.setEmployeeId(loginUser.getUserId());
logEntry.setEmployeeAccount("系统"); logEntry.setEmployeeAccount(loginUser.getUsername());
logCustomProductDomainService.save(logEntry); logCustomProductDomainService.save(logEntry);
} }
...@@ -1232,13 +1513,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1232,13 +1513,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* 批量记录产品日志 * 批量记录产品日志
*/ */
private void saveLogBatch(String action, List<Integer> productIds) { private void saveLogBatch(String action, List<Integer> productIds) {
LoginUser loginUser = SecurityUtils.getLoginUser();
List<LogCustomProductEntity> logs = new ArrayList<>(); List<LogCustomProductEntity> logs = new ArrayList<>();
for (Integer productId : productIds) { for (Integer productId : productIds) {
LogCustomProductEntity logEntry = new LogCustomProductEntity(); LogCustomProductEntity logEntry = new LogCustomProductEntity();
logEntry.setProductId(productId); logEntry.setProductId(productId);
logEntry.setDescription(action); logEntry.setDescription(action);
logEntry.setEmployeeId(-1); logEntry.setEmployeeId(loginUser.getUserId());
logEntry.setEmployeeAccount("系统"); logEntry.setEmployeeAccount(loginUser.getUsername());
logs.add(logEntry); logs.add(logEntry);
} }
logCustomProductDomainService.saveBatch(logs); logCustomProductDomainService.saveBatch(logs);
...@@ -1294,7 +1576,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1294,7 +1576,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.collect(Collectors.toList())); .collect(Collectors.toList()));
// 工厂价格 // 工厂价格
fullVO.setFactoryPriceList(factoryPrices.stream() fullVO.setFactoryPriceList(factoryPrices.stream()
.map(e -> BeanMapper.mapper().convert(e, CustomProductFactoryPriceRelVO.class)) .map(e -> BeanMapper.snakeCase().convert(e, CustomProductFactoryPriceRelSnakeVO.class))
.collect(Collectors.toList())); .collect(Collectors.toList()));
// 关联 ID 列表 // 关联 ID 列表
......
...@@ -70,9 +70,9 @@ public class DiyUserServiceImpl implements DiyUserService { ...@@ -70,9 +70,9 @@ public class DiyUserServiceImpl implements DiyUserService {
// 工厂价格关联的 sales_price(对齐 TS:348-352) // 工厂价格关联的 sales_price(对齐 TS:348-352)
if (vo.getFactoryPriceList() != null) { if (vo.getFactoryPriceList() != null) {
for (CustomProductFactoryPriceRelVO fp : vo.getFactoryPriceList()) { for (CustomProductFactoryPriceRelSnakeVO fp : vo.getFactoryPriceList()) {
if (fp.getSalesPrice() != null) { if (fp.getSales_price() != null) {
fp.setSalesPrice(fp.getSalesPrice().multiply(discountRate).setScale(2, RoundingMode.HALF_UP)); fp.setSales_price(fp.getSales_price().multiply(discountRate).setScale(2, RoundingMode.HALF_UP));
} }
} }
} }
......
...@@ -22,7 +22,7 @@ import java.util.Date; ...@@ -22,7 +22,7 @@ import java.util.Date;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "VO") @Schema(description = "VO")
public class CustomProductFactoryPriceRelVO implements Serializable { public class CustomProductFactoryPriceRelSnakeVO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -36,61 +36,61 @@ public class CustomProductFactoryPriceRelVO implements Serializable { ...@@ -36,61 +36,61 @@ public class CustomProductFactoryPriceRelVO implements Serializable {
* custom_product_info表id * custom_product_info表id
*/ */
@Schema(description = "custom_product_info表id") @Schema(description = "custom_product_info表id")
private Integer productId; private Integer product_id;
/** /**
* custom_product_item表id * custom_product_item表id
*/ */
@Schema(description = "custom_product_item表id") @Schema(description = "custom_product_item表id")
private Integer itemId; private Integer item_id;
/** /**
* custom_product_item表sku * custom_product_item表sku
*/ */
@Schema(description = "custom_product_item表sku") @Schema(description = "custom_product_item表sku")
private String itemSku; private String item_sku;
/** /**
* db_diy表id * db_diy表id
*/ */
@Schema(description = "db_diy表id") @Schema(description = "db_diy表id")
private Integer factoryId; private Integer factory_id;
/** /**
* 工厂价格 * 工厂价格
*/ */
@Schema(description = "工厂价格") @Schema(description = "工厂价格")
private BigDecimal factoryPrice; private BigDecimal factory_price;
/** /**
* 销售价 * 销售价
*/ */
@Schema(description = "销售价") @Schema(description = "销售价")
private BigDecimal salesPrice; private BigDecimal sales_price;
/** /**
* 工厂币种 * 工厂币种
*/ */
@Schema(description = "工厂币种") @Schema(description = "工厂币种")
private String factoryCurrencyCode; private String factory_currency_code;
/** /**
* 售卖币种 * 售卖币种
*/ */
@Schema(description = "售卖币种") @Schema(description = "售卖币种")
private String salesCurrencyCode; private String sales_currency_code;
/** /**
* *
*/ */
@Schema(description = "") @Schema(description = "")
private Date createTime; private Date create_time;
/** /**
* *
*/ */
@Schema(description = "") @Schema(description = "")
private Date updateTime; private Date update_time;
} }
...@@ -153,7 +153,7 @@ public class CustomProductInfoSnakeVO implements Serializable { ...@@ -153,7 +153,7 @@ public class CustomProductInfoSnakeVO implements Serializable {
private ProductRemarkVO productCnRemark; private ProductRemarkVO productCnRemark;
@Schema(description = "工厂价格关联列表") @Schema(description = "工厂价格关联列表")
private List<CustomProductFactoryPriceRelVO> factoryPriceList; private List<CustomProductFactoryPriceRelSnakeVO> factoryPriceList;
@Schema(description = "尺码图片列表(type=1)") @Schema(description = "尺码图片列表(type=1)")
private List<CustomProductImageSnakeVO> sizeList; private List<CustomProductImageSnakeVO> sizeList;
......
...@@ -10,6 +10,7 @@ import java.io.Serial; ...@@ -10,6 +10,7 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* Model * Model
...@@ -242,5 +243,8 @@ public class CustomProductInfoVO implements Serializable { ...@@ -242,5 +243,8 @@ public class CustomProductInfoVO implements Serializable {
@Schema(description = "默认模SKU") @Schema(description = "默认模SKU")
private String diySku; private String diySku;
@Schema(description = "绑定的DIY模板是否已上架(ERP专用)")
private Boolean diyShelfStatus;
} }
...@@ -9,6 +9,7 @@ import java.io.Serial; ...@@ -9,6 +9,7 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* Model * Model
...@@ -319,5 +320,8 @@ public class DbDiyVO implements Serializable { ...@@ -319,5 +320,8 @@ public class DbDiyVO implements Serializable {
@Schema(description = "模备注") @Schema(description = "模备注")
private String diyRemark; private String diyRemark;
@Schema(description = "效果图列表")
private List<DbDiyXiaoguotuVO> xiaoguotuList;
} }
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;
/**
* DIY 效果图 VO
*
* @author Lizh
* @date 2026-06-10
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "DIY 效果图")
public class DbDiyXiaoguotuVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private Integer id;
@Schema(description = "标题")
private String title;
@Schema(description = "排序")
private Integer idx;
@Schema(description = "效果图主图")
private String imgUrl;
@Schema(description = "PSD 链接")
private String psdUrl;
@Schema(description = "颜色 ID")
private Integer colorId;
@Schema(description = "宽度")
private Float width;
@Schema(description = "高度")
private Float height;
@Schema(description = "分辨率")
private Integer dpi;
@Schema(description = "关联的 DIY 模板 ID")
private Integer diyId;
@Schema(description = "状态:1 正常 0 禁用")
private Integer status;
}
...@@ -37,7 +37,7 @@ public class LogCustomProductEntity implements Serializable { ...@@ -37,7 +37,7 @@ public class LogCustomProductEntity implements Serializable {
* 操作人id * 操作人id
*/ */
@TableField("employee_id") @TableField("employee_id")
private Integer employeeId; private Long employeeId;
/** /**
* 操作人账号 * 操作人账号
*/ */
......
...@@ -5,6 +5,8 @@ import com.jomalls.custom.mapper.BaseMapper; ...@@ -5,6 +5,8 @@ import com.jomalls.custom.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* @author Lizh * @author Lizh
* @version 0.01 * @version 0.01
...@@ -21,4 +23,16 @@ public interface CustomProductInfoMapper extends BaseMapper<CustomProductInfoEnt ...@@ -21,4 +23,16 @@ public interface CustomProductInfoMapper extends BaseMapper<CustomProductInfoEnt
* @return 商品实体,未找到返回 null * @return 商品实体,未找到返回 null
*/ */
CustomProductInfoEntity selectBySku(@Param("sku") String sku); CustomProductInfoEntity selectBySku(@Param("sku") String sku);
/**
* ERP 权限过滤:查询指定用户可见的商品 ID 列表
* <p>
* 对齐 TS erpPage 中的 native SQL(TS:686-706)。
* 产品可见条件:(用户已绑定 OR 产品未绑定任何人) AND 用户未被拉黑。
* 使用单次 JOIN 查询避免 N+1 性能问题。
*
* @param userId 当前用户 ID
* @return 符合条件的商品 ID 列表(已去重)
*/
List<Integer> selectIdsByErpPermission(@Param("userId") Integer userId);
} }
...@@ -3,6 +3,8 @@ package com.jomalls.custom.domain.service; ...@@ -3,6 +3,8 @@ package com.jomalls.custom.domain.service;
import com.jomalls.custom.dal.entity.CustomProductInfoEntity; import com.jomalls.custom.dal.entity.CustomProductInfoEntity;
import com.jomalls.custom.service.IBaseService; import com.jomalls.custom.service.IBaseService;
import java.util.List;
/** /**
* @author Lizh * @author Lizh
* @version 0.01 * @version 0.01
...@@ -18,5 +20,13 @@ public interface CustomProductInfoDomainService extends IBaseService<CustomProdu ...@@ -18,5 +20,13 @@ public interface CustomProductInfoDomainService extends IBaseService<CustomProdu
* @return 商品实体,未找到返回 null * @return 商品实体,未找到返回 null
*/ */
CustomProductInfoEntity getBySku(String sku); CustomProductInfoEntity getBySku(String sku);
/**
* ERP 权限过滤:查询指定用户可见的商品 ID 列表
*
* @param userId 当前用户 ID
* @return 符合条件的商品 ID 列表(已去重)
*/
List<Integer> selectIdsByErpPermission(Integer userId);
} }
...@@ -9,6 +9,8 @@ import org.apache.ibatis.session.SqlSessionFactory; ...@@ -9,6 +9,8 @@ import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
/** /**
* @author Lizh * @author Lizh
...@@ -28,4 +30,9 @@ public class CustomProductInfoDomainServiceImpl extends BaseServiceImpl<CustomPr ...@@ -28,4 +30,9 @@ public class CustomProductInfoDomainServiceImpl extends BaseServiceImpl<CustomPr
public CustomProductInfoEntity getBySku(String sku) { public CustomProductInfoEntity getBySku(String sku) {
return baseMapper.selectBySku(sku); return baseMapper.selectBySku(sku);
} }
@Override
public List<Integer> selectIdsByErpPermission(Integer userId) {
return baseMapper.selectIdsByErpPermission(userId);
}
} }
\ No newline at end of file
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
<!-- 批量插入 --> <!-- 批量插入 -->
<insert id="insertBatchSomeColumn"> <insert id="insertBatchSomeColumn">
INSERT INTO custom_product_factory_price_rel (product_id, item_id, item_sku, factory_id, factory_price, sales_price, factory_currency_code, sales_currency_code, create_time, update_time) VALUES INSERT INTO custom_product_factory_price_rel (product_id, item_id, item_sku, factory_id, factory_price, sales_price, factory_currency_code, sales_currency_code) VALUES
<foreach collection="list" item="item" separator=","> <foreach collection="list" item="item" separator=",">
(#{item.productId}, #{item.itemId}, #{item.itemSku}, #{item.factoryId}, #{item.factoryPrice}, #{item.salesPrice}, #{item.factoryCurrencyCode}, #{item.salesCurrencyCode}, #{item.createTime}, #{item.updateTime}) (#{item.productId}, #{item.itemId}, #{item.itemSku}, #{item.factoryId}, #{item.factoryPrice}, #{item.salesPrice}, #{item.factoryCurrencyCode}, #{item.salesCurrencyCode})
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
<!-- 批量插入 --> <!-- 批量插入 -->
<insert id="insertBatchSomeColumn"> <insert id="insertBatchSomeColumn">
INSERT INTO custom_product_image (product_id, image_url, sort, type, create_time) VALUES INSERT INTO custom_product_image (product_id, image_url, sort, type) VALUES
<foreach collection="list" item="item" separator=","> <foreach collection="list" item="item" separator=",">
(#{item.productId}, #{item.imageUrl}, #{item.sort}, #{item.type}, #{item.createTime}) (#{item.productId}, #{item.imageUrl}, #{item.sort}, #{item.type})
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>
...@@ -90,6 +90,21 @@ ...@@ -90,6 +90,21 @@
LIMIT 1 LIMIT 1
</select> </select>
<!-- ERP 权限过滤:查询用户可见的商品 ID 列表 -->
<!-- 对齐 TS erpPage native SQL(TS:686-706)
产品可见条件:
(rel.diy_user_id = :userId OR rel.diy_user_id IS NULL) — 用户已绑定 OR 产品未绑定任何人
AND
(bl.diy_user_id != :userId OR bl.diy_user_id IS NULL) — 用户未被拉黑 -->
<select id="selectIdsByErpPermission" resultType="java.lang.Integer">
SELECT DISTINCT info.id
FROM custom_product_info info
LEFT JOIN custom_product_diy_user_rel rel ON info.id = rel.product_id
LEFT JOIN custom_product_blacklist bl ON info.id = bl.product_id
WHERE (bl.diy_user_id != #{userId} OR bl.diy_user_id IS NULL)
AND (rel.diy_user_id = #{userId} OR rel.diy_user_id IS NULL)
</select>
<!-- 批量插入 --> <!-- 批量插入 -->
<insert id="insertBatchSomeColumn"> <insert id="insertBatchSomeColumn">
INSERT INTO custom_product_info (sku, title, name, img_url, category_id, weight, purchasing_min, factory_price, sales_price, sales_price_max, status, property1_cate_id, property2_cate_id, property3_cate_id, property1_enname, property2_enname, property3_enname, color_images, material, print_type, product_no, origin_code, origin_name_cn, origin_name_en, currency_code, currency_name, product_type, factory_id, factory_code, processing, create_time, update_time, sort, diy_id, diy_sku) VALUES INSERT INTO custom_product_info (sku, title, name, img_url, category_id, weight, purchasing_min, factory_price, sales_price, sales_price_max, status, property1_cate_id, property2_cate_id, property3_cate_id, property1_enname, property2_enname, property3_enname, color_images, material, print_type, product_no, origin_code, origin_name_cn, origin_name_en, currency_code, currency_name, product_type, factory_id, factory_code, processing, create_time, update_time, sort, diy_id, diy_sku) VALUES
......
...@@ -9,6 +9,7 @@ import com.jomalls.custom.utils.R; ...@@ -9,6 +9,7 @@ import com.jomalls.custom.utils.R;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
...@@ -20,10 +21,21 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; ...@@ -20,10 +21,21 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
public class CommonExceptionHandlerAdvice { public class CommonExceptionHandlerAdvice {
/** /**
* JSON 反序列化失败(请求参数格式不匹配)
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<R<Object>> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.debug("[ JSON反序列化失败 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(R.fail(CodeEnum.FAIL.getCode(), "请求参数格式错误: " + e.getLocalizedMessage()));
}
/**
* token验证失败(返回401 未登录或登录已过期) * token验证失败(返回401 未登录或登录已过期)
*/ */
@ExceptionHandler(InvalidTokenException.class) @ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<R<Object>> handleInvalidTokenException(InvalidTokenException e) { public ResponseEntity<R<Object>> handleInvalidTokenException(InvalidTokenException e) {
log.debug("[ Token验证失败 ] {}", e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED) return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(R.fail(CodeEnum.UNAUTHORIZED.getCode(), e.getMessage())); .body(R.fail(CodeEnum.UNAUTHORIZED.getCode(), e.getMessage()));
} }
...@@ -33,6 +45,7 @@ public class CommonExceptionHandlerAdvice { ...@@ -33,6 +45,7 @@ public class CommonExceptionHandlerAdvice {
*/ */
@ExceptionHandler(PermissionDeniedException.class) @ExceptionHandler(PermissionDeniedException.class)
public ResponseEntity<R<Object>> handlePermissionDeniedException(PermissionDeniedException e) { public ResponseEntity<R<Object>> handlePermissionDeniedException(PermissionDeniedException e) {
log.debug("[ 权限拒绝 ] {}", e.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN) return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(R.fail(CodeEnum.FORBIDDEN.getCode(), e.getMessage())); .body(R.fail(CodeEnum.FORBIDDEN.getCode(), e.getMessage()));
} }
...@@ -42,8 +55,9 @@ public class CommonExceptionHandlerAdvice { ...@@ -42,8 +55,9 @@ public class CommonExceptionHandlerAdvice {
*/ */
@ExceptionHandler(ServiceException.class) @ExceptionHandler(ServiceException.class)
public ResponseEntity<R<Object>> handleServiceException(ServiceException e) { public ResponseEntity<R<Object>> handleServiceException(ServiceException e) {
log.debug("[ 业务异常 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail(e.getCode(), e.getMessage())); .body(R.fail(e.getMessage()));
} }
/** /**
...@@ -51,6 +65,7 @@ public class CommonExceptionHandlerAdvice { ...@@ -51,6 +65,7 @@ public class CommonExceptionHandlerAdvice {
*/ */
@ExceptionHandler(RemoteServiceException.class) @ExceptionHandler(RemoteServiceException.class)
public ResponseEntity<R<Object>> handleRemoteServiceException(RemoteServiceException e) { public ResponseEntity<R<Object>> handleRemoteServiceException(RemoteServiceException e) {
log.debug("[ 远程服务异常 ] status={}, {}", e.getStatusCode(), e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail(e.getStatusCode(), e.getMessage())); .body(R.fail(e.getStatusCode(), e.getMessage()));
} }
...@@ -60,6 +75,7 @@ public class CommonExceptionHandlerAdvice { ...@@ -60,6 +75,7 @@ public class CommonExceptionHandlerAdvice {
*/ */
@ExceptionHandler(RuntimeException.class) @ExceptionHandler(RuntimeException.class)
public ResponseEntity<R<Object>> handleRuntimeException(Exception e) { public ResponseEntity<R<Object>> handleRuntimeException(Exception e) {
log.debug("[ 运行时异常 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail(e.getMessage())); .body(R.fail(e.getMessage()));
} }
...@@ -69,6 +85,7 @@ public class CommonExceptionHandlerAdvice { ...@@ -69,6 +85,7 @@ public class CommonExceptionHandlerAdvice {
*/ */
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ResponseEntity<R<Object>> handleException(Exception e) { public ResponseEntity<R<Object>> handleException(Exception e) {
log.debug("[ 未捕获异常 ] {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(R.fail(CodeEnum.FAIL.getCode(), e.getMessage())); .body(R.fail(CodeEnum.FAIL.getCode(), e.getMessage()));
} }
......
...@@ -26,10 +26,6 @@ mybatis-plus.mapper-locations=classpath*:mapper/**/*.xml ...@@ -26,10 +26,6 @@ mybatis-plus.mapper-locations=classpath*:mapper/**/*.xml
mybatis-plus.type-aliases-package=com.jomalls.custom.domain.entity mybatis-plus.type-aliases-package=com.jomalls.custom.domain.entity
mybatis-plus.configuration.call-setters-on-nulls=true mybatis-plus.configuration.call-setters-on-nulls=true
## 数据版本控制
data.version.control.switch=false
default.scp.data.version=1.0
## 时区配置 ## 时区配置
TZ=Asia/Shanghai TZ=Asia/Shanghai
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
<logger name="org.springdoc" level="WARN"/> <logger name="org.springdoc" level="WARN"/>
<!-- ==================== 根配置 ==================== --> <!-- ==================== 根配置 ==================== -->
<root level="INFO"> <root level="DEBUG">
<appender-ref ref="console"/> <appender-ref ref="console"/>
<appender-ref ref="file_info"/> <appender-ref ref="file_info"/>
<appender-ref ref="file_warn"/> <appender-ref ref="file_warn"/>
......
...@@ -2,7 +2,7 @@ package com.jomalls.custom.webapp.controller; ...@@ -2,7 +2,7 @@ package com.jomalls.custom.webapp.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelPageVO;
import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelVO; import com.jomalls.custom.app.vo.CustomProductFactoryPriceRelSnakeVO;
import com.jomalls.custom.app.service.CustomProductFactoryPriceRelService; import com.jomalls.custom.app.service.CustomProductFactoryPriceRelService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
...@@ -33,13 +33,13 @@ public class CustomProductFactoryPriceRelController { ...@@ -33,13 +33,13 @@ public class CustomProductFactoryPriceRelController {
/** /**
* 列表查询接口 * 列表查询接口
* *
* @param customProductFactoryPriceRelVO 条件model * @param customProductFactoryPriceRelSnakeVO 条件model
* @return list集合 * @return list集合
*/ */
@Operation(summary = "列表查询接口", description = "根据条件查询列表接口(不分页)") @Operation(summary = "列表查询接口", description = "根据条件查询列表接口(不分页)")
@RequestMapping(value = "/list", method = RequestMethod.POST) @RequestMapping(value = "/list", method = RequestMethod.POST)
public List<CustomProductFactoryPriceRelVO> list(@RequestBody CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public List<CustomProductFactoryPriceRelSnakeVO> list(@RequestBody CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
return customProductFactoryPriceRelService.list(customProductFactoryPriceRelVO); return customProductFactoryPriceRelService.list(customProductFactoryPriceRelSnakeVO);
} }
/** /**
...@@ -50,7 +50,7 @@ public class CustomProductFactoryPriceRelController { ...@@ -50,7 +50,7 @@ public class CustomProductFactoryPriceRelController {
*/ */
@Operation(summary = "分页列表接口", description = "根据条件查询分页列表接口") @Operation(summary = "分页列表接口", description = "根据条件查询分页列表接口")
@RequestMapping(value = "/pageList", method = RequestMethod.POST) @RequestMapping(value = "/pageList", method = RequestMethod.POST)
public IPage<CustomProductFactoryPriceRelVO> pageList(@RequestBody CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO) { public IPage<CustomProductFactoryPriceRelSnakeVO> pageList(@RequestBody CustomProductFactoryPriceRelPageVO customProductFactoryPriceRelPageVO) {
return customProductFactoryPriceRelService.pageList(customProductFactoryPriceRelPageVO); return customProductFactoryPriceRelService.pageList(customProductFactoryPriceRelPageVO);
} }
...@@ -63,31 +63,31 @@ public class CustomProductFactoryPriceRelController { ...@@ -63,31 +63,31 @@ public class CustomProductFactoryPriceRelController {
*/ */
@Operation(summary = "根据主键id查询详情", description = "根据主键id查询详情") @Operation(summary = "根据主键id查询详情", description = "根据主键id查询详情")
@RequestMapping(value = "/info/{id}", method = RequestMethod.GET) @RequestMapping(value = "/info/{id}", method = RequestMethod.GET)
public CustomProductFactoryPriceRelVO info(@Parameter(description = "主键id", required = true) @PathVariable("id") Integer id) { public CustomProductFactoryPriceRelSnakeVO info(@Parameter(description = "主键id", required = true) @PathVariable("id") Integer id) {
return customProductFactoryPriceRelService.info(id); return customProductFactoryPriceRelService.info(id);
} }
/** /**
* 保存对象 * 保存对象
* *
* @param customProductFactoryPriceRelVO 保存对象 * @param customProductFactoryPriceRelSnakeVO 保存对象
*/ */
@Operation(summary = "保存对象", description = "保存对象") @Operation(summary = "保存对象", description = "保存对象")
@RequestMapping(value = "/save", method = RequestMethod.POST) @RequestMapping(value = "/save", method = RequestMethod.POST)
public void save(@RequestBody @Valid CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public void save(@RequestBody @Valid CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
customProductFactoryPriceRelService.save(customProductFactoryPriceRelVO); customProductFactoryPriceRelService.save(customProductFactoryPriceRelSnakeVO);
} }
/** /**
* 根据id修改对象 * 根据id修改对象
* *
* @param customProductFactoryPriceRelVO 修改对象 * @param customProductFactoryPriceRelSnakeVO 修改对象
*/ */
@Operation(summary = "根据id修改对象", description = "根据id修改对象") @Operation(summary = "根据id修改对象", description = "根据id修改对象")
@RequestMapping(value = "/updateById", method = RequestMethod.PUT) @RequestMapping(value = "/updateById", method = RequestMethod.PUT)
public void updateById(@RequestBody CustomProductFactoryPriceRelVO customProductFactoryPriceRelVO) { public void updateById(@RequestBody CustomProductFactoryPriceRelSnakeVO customProductFactoryPriceRelSnakeVO) {
customProductFactoryPriceRelService.updateById(customProductFactoryPriceRelVO); customProductFactoryPriceRelService.updateById(customProductFactoryPriceRelSnakeVO);
} }
/** /**
......
...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; ...@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.dto.AddBlackListDTO; import com.jomalls.custom.app.dto.AddBlackListDTO;
import com.jomalls.custom.app.dto.BindDiyUserDTO; import com.jomalls.custom.app.dto.BindDiyUserDTO;
import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO; import com.jomalls.custom.app.dto.CustomProductInfoSnakeDTO;
import com.jomalls.custom.app.dto.CustomProductInfoUpdateDTO; import com.jomalls.custom.app.dto.CustomProductInfoUpdateSnakeDTO;
import com.jomalls.custom.app.enums.CustomProductInfoStatusEnum; import com.jomalls.custom.app.enums.CustomProductInfoStatusEnum;
import com.jomalls.custom.app.service.CustomProductInfoService; import com.jomalls.custom.app.service.CustomProductInfoService;
import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO; import com.jomalls.custom.app.vo.CustomProductInfoSnakeVO;
...@@ -51,7 +51,7 @@ public class CustomProductInfoController { ...@@ -51,7 +51,7 @@ public class CustomProductInfoController {
@Operation(summary = "组合更新商品", description = "事务内处理主表及子表的增/删/改差异") @Operation(summary = "组合更新商品", description = "事务内处理主表及子表的增/删/改差异")
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody @Valid CustomProductInfoUpdateDTO dto) { public void update(@RequestBody @Valid CustomProductInfoUpdateSnakeDTO dto) {
customProductInfoService.updateFull(dto); customProductInfoService.updateFull(dto);
} }
......
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