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