diff --git a/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleTrackingServiceImpl.java b/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleTrackingServiceImpl.java index 8c57e48c..70b9d192 100644 --- a/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleTrackingServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleTrackingServiceImpl.java @@ -31,6 +31,12 @@ import java.util.Objects; @Slf4j @Service +/** + * 样品追踪模块核心实现: + * 1) 维护 tracking 表的增删改查; + * 2) 将 tracking 数据同步到 NPI 正式表(项目/物料/打样); + * 3) 同步后执行 ID 对齐,保证 tracking 与 NPI 使用同一主键,便于后续跨表追溯。 + */ public class SampleTrackingServiceImpl implements SampleTrackingService { @Autowired @@ -249,6 +255,8 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { throw new RuntimeException("当前项目物料下无可同步打样单: " + oldProjectPartId); } List syncedIds = new ArrayList<>(); + // 批量同步时每轮都重新查一次列表: + // 因为单条同步会触发 trackingId 对齐(ID 可能变化),使用最新列表可避免漏同步/重复同步。 while (true) { Integer pendingTrackingId = null; currentTrackingIds = sampleTrackingMapper.selectProofTrackingIdsByProjectPartId(syncedProjectPartId); @@ -286,6 +294,8 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (!missingFields.isEmpty()) { throw new RuntimeException("项目追踪记录缺少以下必填项,无法同步: " + String.join("、", missingFields)); } + // 优先按“trackingId == npiId”的目标查 NPI: + // 若已存在则更新,不存在则插入,后续再统一做 tracking ID 对齐。 Project existedNpiProject = projectMapper.selectById(trackingProjectId); Integer npiProjectId = existedNpiProject == null ? null : existedNpiProject.getProjectId(); if (npiProjectId == null) { @@ -303,6 +313,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { trackingProject.setUpdateBy(operator); sampleTrackingMapper.updateNpiProjectFromTracking(trackingProject); } + // 对齐 tracking 主键到 NPI 主键,避免后续依赖方出现“同业务不同ID”的维护成本。 alignTrackingProjectId(trackingProjectId, npiProjectId, operator); return npiProjectId; } @@ -317,7 +328,9 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (!missingPartFields.isEmpty()) { throw new RuntimeException("项目物料追踪记录缺少以下必填项,无法同步: " + String.join("、", missingPartFields)); } + // 物料依赖项目主键,先确保项目已完成同步并拿到最终 NPI 项目ID。 Integer npiProjectId = syncProjectInfoInternal(trackingPart.getProjectId(), operator); + // 重新读取一次 tracking 数据,避免并发修改导致使用旧快照覆盖新值。 ProjectPartTrackingData latestPart = sampleTrackingMapper.selectProjectPartTrackingById(trackingProjectPartId); if (latestPart == null) { throw new RuntimeException("项目物料追踪记录不存在: " + trackingProjectPartId); @@ -345,8 +358,10 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { latestPart.setUpdateBy(operator); sampleTrackingMapper.updateNpiProjectPartFromTracking(latestPart); } + // 主键对齐放在最后执行,保证下游打样记录引用到最终 projectPartId。 alignTrackingProjectPartId(trackingProjectPartId, npiProjectPartId, operator); latestPart.setProjectPartId(npiProjectPartId); + // 邮件放在事务提交后触发,避免邮件异常把本次同步回滚。 registerProjectPartSyncMailAfterCommit(latestPart, operator); return npiProjectPartId; } @@ -380,6 +395,8 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { } Integer npiProofingId = null; + // 先按 trackingId 查,再按 proofingId 查: + // 用于兼容历史数据中“trackingId/proofingId已部分对齐”的场景,避免重复插入。 ProofTrackingData existedByTrackingId = sampleTrackingMapper.selectNpiProofingById(trackingProofingId); if (existedByTrackingId != null) { npiProofingId = existedByTrackingId.getProofingId(); @@ -403,8 +420,10 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { latestTrackingProof.setUpdateBy(operator); sampleTrackingMapper.updateNpiProofingFromTracking(latestTrackingProof); } + // 对齐打样 tracking 主键,同时维护日志/文档关联表,保证链路内所有 proofingId 一致。 alignTrackingProofingId(trackingProofingId, npiProofingId, operator); sampleTrackingMapper.updateProofTrackingProofingIdByTrackingId(npiProofingId, npiProofingId, operator); + // 与 EAM“新增打样”保持一致:主记录落库后补齐打样文档清单初始化。 ensureProofingDocumentAfterSync(npiProofingId, operator); return npiProofingId; } @@ -413,6 +432,8 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (Objects.equals(oldTrackingProjectId, newTrackingProjectId)) { return; } + // 若目标ID已被其他 tracking 记录占用,先把占位记录整体迁移到新ID,再回填当前记录。 + // 这样可以避免直接覆盖造成数据丢失,并保证过程可追溯。 Integer existedId = sampleTrackingMapper.existsProjectInfoTrackingId(newTrackingProjectId); if (existedId != null && !Objects.equals(existedId, oldTrackingProjectId)) { Integer replacementId = sampleTrackingMapper.selectNextProjectInfoTrackingId(); @@ -433,6 +454,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (Objects.equals(oldTrackingProjectPartId, newTrackingProjectPartId)) { return; } + // 与项目ID对齐策略一致:先腾挪占位记录,再执行当前记录主键对齐。 Integer existedId = sampleTrackingMapper.existsProjectPartTrackingId(newTrackingProjectPartId); if (existedId != null && !Objects.equals(existedId, oldTrackingProjectPartId)) { Integer replacementId = sampleTrackingMapper.selectNextProjectPartTrackingId(); @@ -451,6 +473,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (Objects.equals(oldTrackingProofingId, newTrackingProofingId)) { return; } + // 打样ID冲突处理除主表外,还要同步维护日志与文档表引用,保证关联关系不断链。 Integer existedId = sampleTrackingMapper.existsProofTrackingId(newTrackingProofingId); if (existedId != null && !Objects.equals(existedId, oldTrackingProofingId)) { Integer replacementId = sampleTrackingMapper.selectNextProofTrackingId(); @@ -474,6 +497,8 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (trackingProjectId == null || trackingProjectPartId == null) { throw new RuntimeException("打样记录缺少项目或项目物料ID,请先同步项目和物料"); } + // 这里不是仅校验“存在”,还要校验“关系一致”: + // 防止脏数据把打样同步到错误的项目/物料下。 ProjectInfoTrackingData trackingProject = sampleTrackingMapper.selectProjectInfoTrackingById(trackingProjectId); if (trackingProject == null) { throw new RuntimeException("项目追踪记录不存在: " + trackingProjectId); @@ -533,6 +558,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { proofingDocumentData.setProjectPartId(syncedTracking.getProjectPartId()); proofingDocumentData.setcProjectTypeDb(syncedTracking.getProjectCategory()); proofingDocumentData.setCreateBy(resolveOperator(syncedTracking.getCreateBy(), operator)); + // 复用现有文档初始化服务,避免在 sampleTracking 侧重复维护文档生成规则。 proofingInformationService.proofingDocumentSave(proofingDocumentData); } @@ -543,6 +569,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { try { ProofingInformationData mailData = new ProofingInformationData(); BeanUtils.copyProperties(syncedPart, mailData); + // 邮件模板字段分散在项目/物料两张 tracking 表,按“物料优先、项目兜底”组装。 ProjectInfoTrackingData trackingProject = sampleTrackingMapper.selectProjectInfoTrackingById(syncedPart.getProjectId()); if (trackingProject != null) { if (isBlank(mailData.getProjectNo())) { @@ -580,6 +607,7 @@ public class SampleTrackingServiceImpl implements SampleTrackingService { if (syncedPart == null) { return; } + // 使用快照而不是原对象,避免事务提交前后对象被其他逻辑修改。 ProjectPartTrackingData mailSnapshot = new ProjectPartTrackingData(); BeanUtils.copyProperties(syncedPart, mailSnapshot); if (TransactionSynchronizationManager.isSynchronizationActive()) {