diff --git a/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java b/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java index 11cc1010..f2e0c5af 100644 --- a/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java +++ b/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java @@ -57,6 +57,7 @@ public interface ErrorCodeConstants { ErrorCode MATERIAL_CLASSES_NOT_EXISTS = new ErrorCode(1_027_101_003, "物料分类不存在"); ErrorCode DEPARTMENT_MATERIAL_NOT_EXISTS = new ErrorCode(1_027_101_004, "组织物料不存在"); ErrorCode MATERIAL_HAS_CLASSES_NOT_EXISTS = new ErrorCode(1_027_101_004, "物料持有属性不存在"); + ErrorCode MATERIAL_HAS_PROPERTIES_CODE_DUPLICATE = new ErrorCode(1_027_101_106, "同一物料下该属性编码已存在"); ErrorCode MATERIAL_CLASSES_HIERARCHY_INVALID = new ErrorCode(1_027_101_101, "物料分类层级不符合 1-2-3 的层级规则"); ErrorCode MATERIAL_CLASSES_PARENT_NOT_EXISTS = new ErrorCode(1_027_101_102, "上级物料分类不存在"); diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/MaterialInfomationController.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/MaterialInfomationController.java index c596fe40..bbade756 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/MaterialInfomationController.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/MaterialInfomationController.java @@ -80,6 +80,14 @@ public class MaterialInfomationController { return success(materialInfomation); } + @GetMapping("/list-by-ids") + @Operation(summary = "按 ID 批量获得物料信息") + @Parameter(name = "ids", description = "编号集合", required = true) + @PreAuthorize("@ss.hasPermission('base:material-infomation:query')") + public CommonResult> getMaterialInfomationListByIds(@RequestParam("ids") List ids) { + return success(materialInfomationService.getMaterialInfomationListByIds(ids)); + } + @GetMapping("/page") @Operation(summary = "获得物料信息分页") @PreAuthorize("@ss.hasPermission('base:material-infomation:query')") diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationFlatAttributeRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationFlatAttributeRespVO.java new file mode 100644 index 00000000..82aaf893 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationFlatAttributeRespVO.java @@ -0,0 +1,50 @@ +package com.zt.plat.module.base.controller.admin.base.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 展平后的物料属性(按属性编码为键) + */ +@Data +public class MaterialInfomationFlatAttributeRespVO { + + @Schema(description = "属性ID") + private Long propertiesId; + + @Schema(description = "属性编码") + private String propertiesCode; + + @Schema(description = "属性名称") + private String propertiesName; + + @Schema(description = "属性数据类型") + private String dataType; + + @Schema(description = "属性值(编码/原值)") + private String value; + + @Schema(description = "属性值展示(字典标签,若无字典则原值)") + private String valueLabel; + + @Schema(description = "字典类型ID") + private Long dictTypeId; + + @Schema(description = "计量单位ID") + private Long unitId; + + @Schema(description = "计量单位名称") + private String unitName; + + @Schema(description = "计量单位符号") + private String unitSymbol; + + @Schema(description = "是否关键属性") + private Integer isKey; + + @Schema(description = "是否计量定价") + private Integer isMetering; + + @Schema(description = "排序号") + private Long sort; +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationPropertyRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationPropertyRespVO.java new file mode 100644 index 00000000..9f8233d9 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationPropertyRespVO.java @@ -0,0 +1,50 @@ +package com.zt.plat.module.base.controller.admin.base.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 物料属性(持有属性)行展示 VO + */ +@Data +public class MaterialInfomationPropertyRespVO { + + @Schema(description = "属性ID") + private Long propertiesId; + + @Schema(description = "属性编码") + private String propertiesCode; + + @Schema(description = "属性名称") + private String propertiesName; + + @Schema(description = "属性数据类型") + private String dataType; + + @Schema(description = "属性值(编码/原值)") + private String dictionaryDataValue; + + @Schema(description = "属性值展示(字典标签,若无字典则原值)") + private String valueLabel; + + @Schema(description = "字典类型ID") + private Long dictionaryTypeId; + + @Schema(description = "计量单位ID") + private Long unitId; + + @Schema(description = "计量单位名称") + private String unitName; + + @Schema(description = "计量单位符号") + private String unitSymbol; + + @Schema(description = "是否关键属性") + private Integer isKey; + + @Schema(description = "是否计量定价") + private Integer isMetering; + + @Schema(description = "排序号") + private Long sort; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationRespVO.java index 7fc8b56c..e89b25b9 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationRespVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationRespVO.java @@ -2,38 +2,56 @@ package com.zt.plat.module.base.controller.admin.base.vo; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.Map; @Schema(description = "管理后台 - 物料信息 Response VO") @Data @ExcelIgnoreUnannotated public class MaterialInfomationRespVO { + @JsonIgnore @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3326") @ExcelProperty("主键ID") private Long id; + @JsonIgnore @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("物料编码") private String code; + @JsonIgnore @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") @ExcelProperty("物料名称") private String name; + @JsonIgnore @Schema(description = "分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @ExcelProperty("分类ID") private Long classesId; + @JsonIgnore @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("备注") private String remark; + @JsonIgnore @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("创建时间") private LocalDateTime createTime; + @JsonIgnore + @Schema(description = "物料基础字段 + 属性编码->原值的动态键值,基础字段优先,序列化时直接展开为顶层字段") + private Map flatAttributes; + + @JsonAnyGetter + public Map getFlatAttributes() { + return flatAttributes; + } + } \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationSimpleRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationSimpleRespVO.java index 3528b5f9..cabc321d 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationSimpleRespVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationSimpleRespVO.java @@ -1,26 +1,44 @@ package com.zt.plat.module.base.controller.admin.base.vo; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.util.Map; + /** * 精简的物料信息 Response VO */ @Data public class MaterialInfomationSimpleRespVO { + @JsonIgnore @Schema(description = "物料信息ID", example = "1024") private Long id; + @JsonIgnore @Schema(description = "物料编码") private String code; + @JsonIgnore @Schema(description = "物料名称") private String name; + @JsonIgnore @Schema(description = "分类ID") private Long classesId; + @JsonIgnore @Schema(description = "备注") private String remark; + + @JsonIgnore + @Schema(description = "物料基础字段 + 属性编码->原值的动态键值,基础字段优先,序列化时直接展开为顶层字段") + private Map flatAttributes; + + @JsonAnyGetter + public Map getFlatAttributes() { + return flatAttributes; + } } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationValueLabelVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationValueLabelVO.java new file mode 100644 index 00000000..2b507c52 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/base/vo/MaterialInfomationValueLabelVO.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.base.controller.admin.base.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 字典值与标签映射的展示 VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MaterialInfomationValueLabelVO { + + @Schema(description = "字典标签或原值") + private String label; + + @Schema(description = "字典类型ID") + private Long dictTypeId; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/masterdata/MdmMaterialViewDO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/masterdata/MdmMaterialViewDO.java index ce5b59ac..70a78546 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/masterdata/MdmMaterialViewDO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/masterdata/MdmMaterialViewDO.java @@ -20,6 +20,7 @@ public class MdmMaterialViewDO { private String shortDescription; private String longDescription; private String chalcoCode; + private String zhongtongCode; private String majorClassCode; private String majorClassName; private String specification; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationService.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationService.java index 74c7ad7d..3cd4dea0 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationService.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationService.java @@ -10,6 +10,7 @@ import com.zt.plat.module.base.dal.dataobject.base.MaterialInfomationDO; import jakarta.validation.Valid; import java.util.List; +import java.util.Collection; /** * 物料信息 Service 接口 @@ -72,4 +73,12 @@ public interface MaterialInfomationService { * @return 精简分页列表 */ PageResult getMaterialInfomationSimplePage(MaterialInfomationSimplePageReqVO pageReqVO); + + /** + * 按 ID 批量查询物料信息(含持有属性) + * + * @param ids 物料 ID 集合 + * @return 物料信息列表 + */ + List getMaterialInfomationListByIds(Collection ids); } \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java index fcc2f08f..30beead0 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java @@ -14,9 +14,13 @@ import com.zt.plat.module.base.controller.admin.base.vo.MaterialInfomationSimple import com.zt.plat.module.base.controller.admin.base.vo.MaterialInfomationSimpleRespVO; import com.zt.plat.module.base.dal.dao.materialclasses.MaterialClassesMapper; import com.zt.plat.module.base.dal.dao.materialhasclasses.MaterialHasClassesMapper; +import com.zt.plat.module.base.dal.dao.materialhasproperties.MaterialHasPropertiesMapper; +import com.zt.plat.module.base.dal.dao.materialproperties.MaterialPropertiesMapper; import com.zt.plat.module.base.dal.dataobject.base.MaterialInfomationDO; import com.zt.plat.module.base.dal.dataobject.materialclasses.MaterialClassesDO; import com.zt.plat.module.base.dal.dataobject.materialhasclasses.MaterialHasClassesDO; +import com.zt.plat.module.base.dal.dataobject.materialhasproperties.MaterialHasPropertiesDO; +import com.zt.plat.module.base.dal.dataobject.materialproperties.MaterialPropertiesDO; import com.zt.plat.module.base.dal.mysql.base.MaterialInfomationMapper; import com.zt.plat.module.erp.api.ErpExternalApi; import com.zt.plat.module.erp.api.dto.ErpProductiveVersionReqDTO; @@ -25,12 +29,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -59,6 +59,12 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService @Resource private MaterialClassesMapper materialClassesMapper; + @Resource + private MaterialHasPropertiesMapper materialHasPropertiesMapper; + + @Resource + private MaterialPropertiesMapper materialPropertiesMapper; + @Override @Transactional(rollbackFor = Exception.class) public MaterialInfomationRespVO createMaterialInfomation(MaterialInfomationSaveReqVO createReqVO) { @@ -190,6 +196,23 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService return CollUtil.getFirst(buildRespList(Collections.singletonList(info))); } + @Override + public List getMaterialInfomationListByIds(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List infoList = materialInfomationMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(infoList)) { + return Collections.emptyList(); + } + List relationList = materialHasClassesMapper.selectList(MaterialHasClassesDO::getInfomationId, ids); + Map infoClassMap = relationList.stream() + .filter(item -> item.getInfomationId() != null) + .collect(Collectors.toMap(MaterialHasClassesDO::getInfomationId, MaterialHasClassesDO::getClassesId, (existing, replacement) -> existing)); + infoList.forEach(item -> item.setClassesId(infoClassMap.get(item.getId()))); + return buildRespList(infoList); + } + @Override public PageResult getMaterialInfomationPage(MaterialInfomationPageReqVO pageReqVO) { List infomationIds = null; @@ -282,14 +305,28 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService MaterialHasClassesDO::getClassesId, (existing, replacement) -> existing)); } + Map propertyAggregateMap = buildPropertyAggregates( + pageResult.getList().stream().map(MaterialInfomationDO::getId).collect(Collectors.toList())); + Map finalInfoClassMap = infoClassMap; List respList = pageResult.getList().stream().map(item -> { MaterialInfomationSimpleRespVO vo = new MaterialInfomationSimpleRespVO(); - vo.setId(item.getId()); - vo.setCode(item.getCode()); - vo.setName(item.getName()); - vo.setRemark(item.getRemark()); - vo.setClassesId(finalInfoClassMap.get(item.getId())); + Map flatMap = new LinkedHashMap<>(); + flatMap.put("id", item.getId()); + flatMap.put("code", item.getCode()); + flatMap.put("name", item.getName()); + flatMap.put("classesId", finalInfoClassMap.get(item.getId())); + flatMap.put("remark", item.getRemark()); + + MaterialPropertiesAggregate aggregate = propertyAggregateMap.get(item.getId()); + if (aggregate != null && CollUtil.isNotEmpty(aggregate.getAttributes())) { + aggregate.getAttributes().forEach((k, v) -> { + if (!flatMap.containsKey(k)) { + flatMap.put(k, v); + } + }); + } + vo.setFlatAttributes(flatMap); return vo; }).collect(Collectors.toList()); @@ -300,8 +337,89 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } + Map propertyAggregateMap = buildPropertyAggregates( + list.stream().map(MaterialInfomationDO::getId).filter(Objects::nonNull).collect(Collectors.toList())); return list.stream() - .map(item -> BeanUtils.toBean(item, MaterialInfomationRespVO.class)) + .map(item -> { + MaterialInfomationRespVO vo = BeanUtils.toBean(item, MaterialInfomationRespVO.class); + Map flatMap = new LinkedHashMap<>(); + flatMap.put("id", item.getId()); + flatMap.put("code", item.getCode()); + flatMap.put("name", item.getName()); + flatMap.put("classesId", item.getClassesId()); + flatMap.put("remark", item.getRemark()); + flatMap.put("createTime", item.getCreateTime()); + + MaterialPropertiesAggregate aggregate = propertyAggregateMap.get(item.getId()); + if (aggregate != null && CollUtil.isNotEmpty(aggregate.getAttributes())) { + aggregate.getAttributes().forEach((k, v) -> { + // 基础字段优先,存在同名则跳过属性值 + if (!flatMap.containsKey(k)) { + flatMap.put(k, v); + } + }); + } + vo.setFlatAttributes(flatMap); + return vo; + }) .collect(Collectors.toList()); } + + private Map buildPropertyAggregates(Collection infoIds) { + if (CollUtil.isEmpty(infoIds)) { + return Collections.emptyMap(); + } + + List hasPropertiesList = materialHasPropertiesMapper.selectList( + new LambdaQueryWrapperX().in(MaterialHasPropertiesDO::getInfomationId, infoIds)); + if (CollUtil.isEmpty(hasPropertiesList)) { + return Collections.emptyMap(); + } + + Set propertiesIds = hasPropertiesList.stream() + .map(MaterialHasPropertiesDO::getPropertiesId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map propertiesMap = propertiesIds.isEmpty() + ? Collections.emptyMap() + : materialPropertiesMapper.selectBatchIds(propertiesIds).stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(MaterialPropertiesDO::getId, Function.identity())); + + Map> infoPropsMap = hasPropertiesList.stream() + .collect(Collectors.groupingBy(MaterialHasPropertiesDO::getInfomationId)); + + Map result = new HashMap<>(); + for (Map.Entry> entry : infoPropsMap.entrySet()) { + Long infoId = entry.getKey(); + List props = entry.getValue(); + // 按 sort 升序,保持 deterministic,重复 code 时保留首条 + props.sort(Comparator.comparing((MaterialHasPropertiesDO o) -> o.getSort() == null ? Long.MAX_VALUE : o.getSort()) + .thenComparing(MaterialHasPropertiesDO::getPropertiesId, Comparator.nullsLast(Long::compareTo)) + .thenComparing(MaterialHasPropertiesDO::getId, Comparator.nullsLast(Long::compareTo))); + + Map flatMap = new LinkedHashMap<>(); + for (MaterialHasPropertiesDO item : props) { + MaterialPropertiesDO property = propertiesMap.get(item.getPropertiesId()); + String code = property != null ? property.getCode() : null; + if (StrUtil.isBlank(code)) { + continue; + } + if (flatMap.containsKey(code)) { + continue; + } + flatMap.put(code, item.getValue()); + } + + result.put(infoId, new MaterialPropertiesAggregate(flatMap)); + } + + return result; + } + + private record MaterialPropertiesAggregate(Map attributes) { + public Map getAttributes() { + return attributes; + } + } } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/MasterDataSyncServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/MasterDataSyncServiceImpl.java index 3d2e0c2a..651d728e 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/MasterDataSyncServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/MasterDataSyncServiceImpl.java @@ -49,8 +49,6 @@ import java.util.stream.Collectors; @Service public class MasterDataSyncServiceImpl implements MasterDataSyncService { - private static final long ROOT_CLASS_PARENT_ID = 0L; - @Resource private MdmMaterialViewMapper mdmMaterialViewMapper; @Resource @@ -113,6 +111,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { offset += slice.size(); report.incrementProcessedRecords(slice.size()); final List batch = slice; + // 单批落库使用事务,保证本批次原子性与幂等可重试 transactionTemplate.executeWithoutResult(status -> processBatch(batch, propertyDefinitions, report, materialCacheById, materialCacheByCode, categoryCacheByCode)); } @@ -178,7 +177,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { .dataType(definition.getDataType()) .remark("MDM同步自动创建") .build(); - pendingInsertDefinitions.add(current); + pendingInsertDefinitions.add(current); report.incrementInsertedPropertyDefinitions(); } else { boolean needUpdate = false; @@ -298,6 +297,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { } Map mapping = new LinkedHashMap<>(); List pendingMaterialInserts = new ArrayList<>(); + List pendingMaterialUpdates = new ArrayList<>(); // 收集需批量更新的物料主数据 for (MdmMaterialViewDO item : batch) { Long codeId = item.getCodeId(); if (codeId == null || codeId <= 0) { @@ -309,8 +309,8 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { continue; } MaterialInfomationDO current = existingById.get(codeId); - // 名称优先取 MDM 的长描述(desclong),若为空再回退到 desc1 - String name = normalizeValue(item.getLongDescription()); + // 名称优先取 MDM 的短描述(descshort),若为空再回退到 desc1 + String name = normalizeValue(item.getShortDescription()); if (name == null) { name = normalizeValue(item.getMaterialName()); } @@ -345,7 +345,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { update.setId(current.getId()); update.setCode(current.getCode()); update.setName(current.getName()); - materialInfomationMapper.updateById(update); + pendingMaterialUpdates.add(update); report.incrementUpdatedMaterials(); } mapping.put(code, current); @@ -354,6 +354,9 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { if (CollUtil.isNotEmpty(pendingMaterialInserts)) { materialInfomationMapper.insertBatch(pendingMaterialInserts); } + if (CollUtil.isNotEmpty(pendingMaterialUpdates)) { + materialInfomationMapper.updateBatch(pendingMaterialUpdates); + } return mapping; } @@ -374,9 +377,10 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { .distinct() .collect(Collectors.toList()); Map existingRelations = materialHasClassesMapper.selectByInfoIds(infoIds) - .stream() - .collect(Collectors.toMap(MaterialHasClassesDO::getInfomationId, Function.identity(), (a, b) -> a)); - List relationsToInsert = new ArrayList<>(); + .stream() + .collect(Collectors.toMap(MaterialHasClassesDO::getInfomationId, Function.identity(), (a, b) -> a)); + List relationsToInsert = new ArrayList<>(); + List relationsToUpdate = new ArrayList<>(); // 收集需批量更新的物料-分类关系 for (MdmMaterialViewDO item : batch) { String materialCode = normalizeValue(item.getMaterialCode()); MaterialInfomationDO info = materials.get(materialCode); @@ -401,7 +405,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { MaterialHasClassesDO update = new MaterialHasClassesDO(); update.setId(relation.getId()); update.setClassesId(cls.getId()); - materialHasClassesMapper.updateById(update); + relationsToUpdate.add(update); relation.setClassesId(cls.getId()); report.incrementUpdatedClassRelations(); } @@ -409,6 +413,9 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { if (CollUtil.isNotEmpty(relationsToInsert)) { materialHasClassesMapper.insertBatch(relationsToInsert); } + if (CollUtil.isNotEmpty(relationsToUpdate)) { + materialHasClassesMapper.updateBatch(relationsToUpdate); + } } private void upsertMaterialProperties(List batch, @@ -438,6 +445,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { .collect(Collectors.toMap(item -> buildRelationKey(item.getInfomationId(), item.getPropertiesId()), Function.identity(), (a, b) -> a)); List propertyRelationsToInsert = new ArrayList<>(); + List propertyRelationsToUpdate = new ArrayList<>(); // 收集需批量更新的物料属性值 for (MdmMaterialViewDO item : batch) { String materialCode = normalizeValue(item.getMaterialCode()); MaterialInfomationDO info = materials.get(materialCode); @@ -473,7 +481,7 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { update.setId(relation.getId()); update.setValue(value); update.setSort(entry.getKey().getSort()); - materialHasPropertiesMapper.updateById(update); + propertyRelationsToUpdate.add(update); relation.setValue(value); relation.setSort(entry.getKey().getSort()); report.incrementUpdatedPropertyValues(); @@ -483,6 +491,9 @@ public class MasterDataSyncServiceImpl implements MasterDataSyncService { if (CollUtil.isNotEmpty(propertyRelationsToInsert)) { materialHasPropertiesMapper.insertBatch(propertyRelationsToInsert); } + if (CollUtil.isNotEmpty(propertyRelationsToUpdate)) { + materialHasPropertiesMapper.updateBatch(propertyRelationsToUpdate); + } } private String buildRelationKey(Long infoId, Long propertyId) { diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/support/MasterDataPropertyDefinition.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/support/MasterDataPropertyDefinition.java index 33545eef..23363c6b 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/support/MasterDataPropertyDefinition.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/masterdatasync/support/MasterDataPropertyDefinition.java @@ -14,21 +14,22 @@ import java.util.function.Function; @RequiredArgsConstructor public enum MasterDataPropertyDefinition { - BASE_UNIT_CODE("MTRL_BASE_UNIT_CODE", "基本计量单位编码", 50L, MdmMaterialViewDO::getBaseUnitCode, "STRING"), - BASE_UNIT_NAME("MTRL_BASE_UNIT_NAME", "基本计量单位描述", 60L, MdmMaterialViewDO::getBaseUnitName, "STRING"), - SHORT_DESCRIPTION("MTRL_SHORT_DESC", "短描述", 70L, MdmMaterialViewDO::getShortDescription, "STRING"), - LONG_DESCRIPTION("MTRL_LONG_DESC", "长描述", 80L, MdmMaterialViewDO::getLongDescription, "STRING"), - CHALCO_CODE("MTRL_CHALCO_CODE", "中铝编码", 90L, MdmMaterialViewDO::getChalcoCode, "STRING"), - SPECIFICATION("MTRL_SPECIFICATION", "规格", 100L, MdmMaterialViewDO::getSpecification, "STRING"), - MODEL("MTRL_MODEL", "型号", 110L, MdmMaterialViewDO::getModel, "STRING"), - TEXTURE("MTRL_TEXTURE", "材质", 120L, MdmMaterialViewDO::getTexture, "STRING"), - DRAWING_NUMBER("MTRL_DRAWING_NUMBER", "图号", 130L, MdmMaterialViewDO::getDrawingNumber, "STRING"), - ORDER_NUMBER("MTRL_ORDER_NUMBER", "订货号", 140L, MdmMaterialViewDO::getOrderNumber, "STRING"), - OTHER_PARAMETERS("MTRL_OTHER_PARAMETERS", "其它参数", 150L, MdmMaterialViewDO::getOtherParameters, "STRING"), - EQUIPMENT_CATEGORY("MTRL_EQUIPMENT_CATEGORY", "设备类别", 160L, MdmMaterialViewDO::getEquipmentCategory, "STRING"), - MANUFACTURER("MTRL_MANUFACTURER", "主机生产商", 170L, MdmMaterialViewDO::getManufacturer, "STRING"), - SOURCE("MTRL_SOURCE", "来源", 180L, MdmMaterialViewDO::getSource, "STRING"), - RECORD_TIME("MTRL_RECORD_TIME", "记录时间", 190L, + BASE_UNIT_CODE("mtrlBaseUnitCode", "基本计量单位编码", 50L, MdmMaterialViewDO::getBaseUnitCode, "STRING"), + BASE_UNIT_NAME("mtrlBaseUnitName", "基本计量单位描述", 60L, MdmMaterialViewDO::getBaseUnitName, "STRING"), + SHORT_DESCRIPTION("mtrlShortDesc", "短描述", 70L, MdmMaterialViewDO::getShortDescription, "STRING"), + LONG_DESCRIPTION("mtrlLongDesc", "长描述", 80L, MdmMaterialViewDO::getLongDescription, "STRING"), + CHALCO_CODE("mtrlChalcoCode", "中铝编码", 90L, MdmMaterialViewDO::getChalcoCode, "STRING"), + ZHONGTONG_CODE("mtrlZhongtongCode", "中铜编码", 95L, MdmMaterialViewDO::getZhongtongCode, "STRING"), + SPECIFICATION("mtrlSpecification", "规格", 100L, MdmMaterialViewDO::getSpecification, "STRING"), + MODEL("mtrlModel", "型号", 110L, MdmMaterialViewDO::getModel, "STRING"), + TEXTURE("mtrlTexture", "材质", 120L, MdmMaterialViewDO::getTexture, "STRING"), + DRAWING_NUMBER("mtrlDrawingNumber", "图号", 130L, MdmMaterialViewDO::getDrawingNumber, "STRING"), + ORDER_NUMBER("mtrlOrderNumber", "订货号", 140L, MdmMaterialViewDO::getOrderNumber, "STRING"), + OTHER_PARAMETERS("mtrlOtherParameters", "其它参数", 150L, MdmMaterialViewDO::getOtherParameters, "STRING"), + EQUIPMENT_CATEGORY("mtrlEquipmentCategory", "设备类别", 160L, MdmMaterialViewDO::getEquipmentCategory, "STRING"), + MANUFACTURER("mtrlManufacturer", "主机生产商", 170L, MdmMaterialViewDO::getManufacturer, "STRING"), + SOURCE("mtrlSource", "来源", 180L, MdmMaterialViewDO::getSource, "STRING"), + RECORD_TIME("mtrlRecordTime", "记录时间", 190L, source -> source.getRecordTime() == null ? null : source.getRecordTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), "DATETIME"); diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java index 1618e5e2..f858fee3 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java @@ -2,19 +2,27 @@ package com.zt.plat.module.base.service.materialhasproperties; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; -import org.springframework.stereotype.Service; import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; -import java.util.*; import com.zt.plat.module.base.controller.admin.materialhasproperties.vo.*; -import com.zt.plat.module.base.dal.dataobject.materialhasproperties.MaterialHasPropertiesDO; -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.framework.common.pojo.PageParam; -import com.zt.plat.framework.common.util.object.BeanUtils; - import com.zt.plat.module.base.dal.dao.materialhasproperties.MaterialHasPropertiesMapper; +import com.zt.plat.module.base.dal.dao.materialproperties.MaterialPropertiesMapper; +import com.zt.plat.module.base.dal.dataobject.materialhasproperties.MaterialHasPropertiesDO; +import com.zt.plat.module.base.dal.dataobject.materialproperties.MaterialPropertiesDO; +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -34,8 +42,12 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe @Resource private MaterialHasPropertiesMapper materialHasPropertiesMapper; + @Resource + private MaterialPropertiesMapper materialPropertiesMapper; + @Override public MaterialHasPropertiesRespVO createMaterialHasProperties(MaterialHasPropertiesSaveReqVO createReqVO) { + validatePropertyCodeUnique(createReqVO.getInfomationId(), createReqVO.getPropertiesId(), null); // 插入 MaterialHasPropertiesDO materialHasProperties = BeanUtils.toBean(createReqVO, MaterialHasPropertiesDO.class); materialHasPropertiesMapper.insert(materialHasProperties); @@ -47,6 +59,7 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe public void updateMaterialHasProperties(MaterialHasPropertiesSaveReqVO updateReqVO) { // 校验存在 validateMaterialHasPropertiesExists(updateReqVO.getId()); + validatePropertyCodeUnique(updateReqVO.getInfomationId(), updateReqVO.getPropertiesId(), updateReqVO.getId()); // 更新 MaterialHasPropertiesDO updateObj = BeanUtils.toBean(updateReqVO, MaterialHasPropertiesDO.class); materialHasPropertiesMapper.updateById(updateObj); @@ -110,7 +123,17 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe } // 去重并按提交顺序插入 - Set dedupKeys = new LinkedHashSet<>(); + Set dedupCodes = new LinkedHashSet<>(); + Set propertiesIds = properties.stream() + .map(MaterialHasPropertiesBatchItemReqVO::getPropertiesId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map propertyMap = propertiesIds.isEmpty() + ? Map.of() + : materialPropertiesMapper.selectBatchIds(propertiesIds).stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(MaterialPropertiesDO::getId, Function.identity())); + for (int i = 0; i < properties.size(); i++) { MaterialHasPropertiesBatchItemReqVO item = properties.get(i); String propIdStr = item.getPropertiesId() == null ? null : String.valueOf(item.getPropertiesId()); @@ -118,13 +141,18 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "属性 ID 不能为空")); continue; } + MaterialPropertiesDO property = propertyMap.get(item.getPropertiesId()); + String code = property != null ? property.getCode() : null; + if (StrUtil.isBlank(code)) { + resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "属性编码不能为空")); + continue; + } if (StrUtil.isBlank(item.getValue())) { resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "属性值不能为空")); continue; } - String key = propIdStr; - if (!dedupKeys.add(key)) { - // 重复的属性直接跳过后续插入,避免唯一冲突 + if (!dedupCodes.add(code)) { + resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "同一物料下属性编码重复")); continue; } MaterialHasPropertiesDO entity = MaterialHasPropertiesDO.builder() @@ -142,4 +170,38 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe return resp; } + /** + * 事务内校验:同一物料下有效属性编码唯一 + */ + private void validatePropertyCodeUnique(Long infoId, Long propertiesId, Long excludeId) { + if (infoId == null || propertiesId == null) { + return; + } + MaterialPropertiesDO property = materialPropertiesMapper.selectById(propertiesId); + if (property == null || StrUtil.isBlank(property.getCode())) { + return; + } + String code = property.getCode(); + List sameCodeProps = materialPropertiesMapper.selectList( + new LambdaQueryWrapperX().eq(MaterialPropertiesDO::getCode, code)); + if (CollUtil.isEmpty(sameCodeProps)) { + return; + } + Set samePropertyIds = sameCodeProps.stream() + .map(MaterialPropertiesDO::getId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (samePropertyIds.isEmpty()) { + return; + } + + Long exists = materialHasPropertiesMapper.selectCount(new LambdaQueryWrapperX() + .eq(MaterialHasPropertiesDO::getInfomationId, infoId) + .in(MaterialHasPropertiesDO::getPropertiesId, samePropertyIds) + .neIfPresent(MaterialHasPropertiesDO::getId, excludeId)); + if (exists != null && exists > 0) { + throw exception(MATERIAL_HAS_PROPERTIES_CODE_DUPLICATE); + } + } + } \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/resources/mapper/masterdata/MdmMaterialViewMapper.xml b/zt-module-base/zt-module-base-server/src/main/resources/mapper/masterdata/MdmMaterialViewMapper.xml index 0e3c31e2..44b1f52e 100644 --- a/zt-module-base/zt-module-base-server/src/main/resources/mapper/masterdata/MdmMaterialViewMapper.xml +++ b/zt-module-base/zt-module-base-server/src/main/resources/mapper/masterdata/MdmMaterialViewMapper.xml @@ -14,6 +14,7 @@ a.descshort AS short_description, a.desclong AS long_description, a.desc86 AS chalco_code, + a.code AS zhongtong_code, b.pur_class AS major_class_code, c.desc1 AS major_class_name, a.desc9 AS specification,