diff --git a/src/main/java/com/xujie/sys/modules/sampletracking/mapper/SampleProofTrackingMapper.java b/src/main/java/com/xujie/sys/modules/sampletracking/mapper/SampleProofTrackingMapper.java
index 4fd2561e..38b944fe 100644
--- a/src/main/java/com/xujie/sys/modules/sampletracking/mapper/SampleProofTrackingMapper.java
+++ b/src/main/java/com/xujie/sys/modules/sampletracking/mapper/SampleProofTrackingMapper.java
@@ -32,6 +32,13 @@ public interface SampleProofTrackingMapper {
@Param("projectId") Integer projectId,
@Param("testPartNo") String testPartNo);
+ int insertProjectInfoTrackingFromNpi(@Param("projectId") Integer projectId,
+ @Param("operateUser") String operateUser);
+
+ int insertProjectPartTrackingFromNpi(@Param("projectId") Integer projectId,
+ @Param("projectPartId") Integer projectPartId,
+ @Param("operateUser") String operateUser);
+
void insertProjectInfoFromOneKey(ProofTrackingData data);
void insertProjectPartFromOneKey(ProofTrackingData data);
diff --git a/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleProofTrackingServiceImpl.java b/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleProofTrackingServiceImpl.java
index f0126ba9..ab8fdc9a 100644
--- a/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleProofTrackingServiceImpl.java
+++ b/src/main/java/com/xujie/sys/modules/sampletracking/service/impl/SampleProofTrackingServiceImpl.java
@@ -62,6 +62,7 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
private static final String DEFAULT_PROCESS_CONFIG_BU = "*";
private static final String DELIVERY_PACKAGE_PROCESS_CODE = "deliveryPackage";
private static final String DELIVERY_PACKAGE_PROCESS_NAME = "Delivery&Package";
+ private static final int NPI_ID_THRESHOLD = 100000;
/**
* 工序定义兜底:当配置表未初始化或查询异常时,仍保持页面可用。
@@ -598,6 +599,8 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
throw new RuntimeException("创建项目信息失败,未返回项目ID");
}
}
+ // 复用 NPI 项目ID 时,必须同步补齐 tracking 项目主档,否则后续物料/打样会出现“仅创建打样主表”的断链。
+ ensureProjectTrackingRowExists(data.getProjectId(), data.getSite(), operateUser);
Integer matchedProjectPartId = resolveExistingOneKeyProjectPartId(data);
if (matchedProjectPartId != null) {
@@ -609,14 +612,41 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
throw new RuntimeException("创建项目物料失败,未返回物料ID");
}
}
+ // 同步补齐 tracking 物料主档,确保列表关联字段、颜色判定与修改弹窗查询链路完整。
+ ensureProjectPartTrackingRowExists(data.getProjectId(), data.getProjectPartId(), data.getSite(), operateUser);
+ // 打样单号来自 ERF 时,若 NPI 中已存在同项目/物料/打样单,需直接复用 NPI 打样ID,确保列表同步标绿。
+ Integer matchedProofingId = resolveExistingOneKeyProofingId(data);
+ if (matchedProofingId != null) {
+ // 对齐规则:命中 NPI 打样时,tracking.proofing_id 与 tracking.id 必须同时使用 NPI 打样ID。
+ data.setProofingId(matchedProofingId);
+ data.setTrackingId(matchedProofingId);
+ } else {
+ data.setProofingId(null);
+ data.setTrackingId(null);
+ }
- try {
- sampleProofTrackingMapper.insertProofTracking(data);
- } catch (Exception e) {
- log.warn("一键创建插入追踪信息失败,尝试按项目物料创建空白追踪记录: projectId={}, projectPartId={}",
- data.getProjectId(), data.getProjectPartId(), e);
+ boolean trackingPersisted = false;
+ if (isTrackingRowExists(data.getTrackingId())) {
+ // 并发场景下同一打样可能已被其他请求先创建,直接复用已存在 tracking 记录。
+ trackingPersisted = true;
+ log.info("一键创建复用已存在 tracking 记录: trackingId={}, projectId={}, projectPartId={}",
+ data.getTrackingId(), data.getProjectId(), data.getProjectPartId());
}
- if (data.getTrackingId() == null) {
+ if (!trackingPersisted) {
+ try {
+ sampleProofTrackingMapper.insertProofTracking(data);
+ trackingPersisted = true;
+ } catch (Exception e) {
+ log.warn("一键创建插入追踪信息失败,projectId={}, projectPartId={}, trackingId={}",
+ data.getProjectId(), data.getProjectPartId(), data.getTrackingId(), e);
+ trackingPersisted = isTrackingRowExists(data.getTrackingId());
+ }
+ }
+ if (!trackingPersisted && matchedProofingId != null) {
+ // 命中 NPI 打样ID时不允许降级到临时 trackingId,否则会破坏“标绿即三表ID一致”规则。
+ throw new RuntimeException("NPI打样单已存在,但tracking主键未能对齐到NPI打样ID: " + matchedProofingId);
+ }
+ if (!trackingPersisted) {
ProofTrackingData fallbackTracking = new ProofTrackingData();
fallbackTracking.setSite(data.getSite());
fallbackTracking.setProjectId(data.getProjectId());
@@ -625,8 +655,9 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
fallbackTracking.setUpdateBy(operateUser);
sampleProofTrackingMapper.insertTrackingFromPart(fallbackTracking);
data.setTrackingId(fallbackTracking.getTrackingId());
+ trackingPersisted = data.getTrackingId() != null;
}
- if (data.getTrackingId() == null) {
+ if (!trackingPersisted || data.getTrackingId() == null) {
throw new RuntimeException("创建打样跟踪失败,未返回跟踪ID");
}
// 业务要求:打样预计完成日期与 Delivery&Package 工序日期保持同步,避免同一记录出现双日期口径。
@@ -1657,6 +1688,16 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
if (isBlank(data.getProjectNo())) {
return null;
}
+ Integer matchedNpiProjectId = sampleTrackingMapper.selectNpiProjectIdBySiteBuNoAndProjectNo(
+ data.getSite(),
+ data.getBuNo(),
+ data.getProjectNo()
+ );
+ if (isNpiAlignedId(matchedNpiProjectId)) {
+ log.info("一键创建命中 NPI 项目,优先复用 NPI 主档ID: site={}, buNo={}, projectNo={}, projectId={}",
+ data.getSite(), data.getBuNo(), data.getProjectNo(), matchedNpiProjectId);
+ return matchedNpiProjectId;
+ }
Integer matchedProjectId = sampleProofTrackingMapper.selectProjectInfoTrackingIdBySiteBuAndProjectNo(
data.getSite(),
data.getBuNo(),
@@ -1674,6 +1715,16 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
if (data.getProjectId() == null || isBlank(data.getTestPartNo())) {
return null;
}
+ Integer matchedNpiProjectPartId = sampleTrackingMapper.selectNpiProjectPartIdBySiteProjectAndPartNo(
+ data.getSite(),
+ data.getProjectId(),
+ data.getTestPartNo()
+ );
+ if (isNpiAlignedId(matchedNpiProjectPartId)) {
+ log.info("一键创建命中 NPI 项目物料,优先复用 NPI 物料ID: site={}, projectId={}, testPartNo={}, projectPartId={}",
+ data.getSite(), data.getProjectId(), data.getTestPartNo(), matchedNpiProjectPartId);
+ return matchedNpiProjectPartId;
+ }
Integer matchedProjectPartId = sampleProofTrackingMapper.selectProjectPartTrackingIdBySiteProjectAndPartNo(
data.getSite(),
data.getProjectId(),
@@ -1686,6 +1737,77 @@ public class SampleProofTrackingServiceImpl implements SampleProofTrackingServic
return matchedProjectPartId;
}
+ private Integer resolveExistingOneKeyProofingId(ProofTrackingData data) {
+ if (isBlank(data.getSite())
+ || data.getProjectId() == null
+ || data.getProjectPartId() == null
+ || isBlank(data.getProofingNo())) {
+ return null;
+ }
+ String normalizedProofingNo = data.getProofingNo().trim();
+ data.setProofingNo(normalizedProofingNo);
+ Integer matchedNpiProofingId = sampleTrackingMapper.selectNpiProofingIdBySiteProjectPartAndProofingNo(
+ data.getSite(),
+ data.getProjectId(),
+ data.getProjectPartId(),
+ normalizedProofingNo
+ );
+ if (isNpiAlignedId(matchedNpiProofingId)) {
+ log.info("一键创建命中 NPI 打样单,复用 NPI 打样ID: site={}, projectId={}, projectPartId={}, proofingNo={}, proofingId={}",
+ data.getSite(), data.getProjectId(), data.getProjectPartId(), normalizedProofingNo, matchedNpiProofingId);
+ return matchedNpiProofingId;
+ }
+ return null;
+ }
+
+ private boolean isNpiAlignedId(Integer idValue) {
+ return idValue != null && idValue > 0 && idValue < NPI_ID_THRESHOLD;
+ }
+
+ private boolean isTrackingRowExists(Integer trackingId) {
+ if (trackingId == null || trackingId <= 0) {
+ return false;
+ }
+ return sampleTrackingMapper.existsProofTrackingId(trackingId) != null;
+ }
+
+ private void ensureProjectTrackingRowExists(Integer projectId, String site, String operateUser) {
+ if (projectId == null) {
+ return;
+ }
+ Integer existedTrackingProjectId = sampleTrackingMapper.existsProjectInfoTrackingId(projectId);
+ if (existedTrackingProjectId != null) {
+ return;
+ }
+ int insertedRows = sampleProofTrackingMapper.insertProjectInfoTrackingFromNpi(projectId, operateUser);
+ Integer recheckedTrackingProjectId = sampleTrackingMapper.existsProjectInfoTrackingId(projectId);
+ if (recheckedTrackingProjectId == null) {
+ throw new RuntimeException("项目主档未落地到 tracking 表,无法创建打样记录: projectId=" + projectId);
+ }
+ if (insertedRows > 0) {
+ log.info("一键创建补齐 tracking 项目主档成功: site={}, projectId={}", site, projectId);
+ }
+ }
+
+ private void ensureProjectPartTrackingRowExists(Integer projectId, Integer projectPartId, String site, String operateUser) {
+ if (projectPartId == null) {
+ return;
+ }
+ Integer existedTrackingProjectPartId = sampleTrackingMapper.existsProjectPartTrackingId(projectPartId);
+ if (existedTrackingProjectPartId != null) {
+ return;
+ }
+ int insertedRows = sampleProofTrackingMapper.insertProjectPartTrackingFromNpi(projectId, projectPartId, operateUser);
+ Integer recheckedTrackingProjectPartId = sampleTrackingMapper.existsProjectPartTrackingId(projectPartId);
+ if (recheckedTrackingProjectPartId == null) {
+ throw new RuntimeException("项目物料主档未落地到 tracking 表,无法创建打样记录: projectPartId=" + projectPartId);
+ }
+ if (insertedRows > 0) {
+ log.info("一键创建补齐 tracking 项目物料主档成功: site={}, projectId={}, projectPartId={}",
+ site, projectId, projectPartId);
+ }
+ }
+
private void validateOneKeyUpdateData(ProofTrackingData data) {
// 业务要求:一键修改仅强制校验料号描述、PjM、Engineer,其它字段允许为空。
if (isBlank(data.getPartDesc())) {
diff --git a/src/main/resources/mapper/sampletracking/SampleProofTrackingMapper.xml b/src/main/resources/mapper/sampletracking/SampleProofTrackingMapper.xml
index 9e078c27..5f566adb 100644
--- a/src/main/resources/mapper/sampletracking/SampleProofTrackingMapper.xml
+++ b/src/main/resources/mapper/sampletracking/SampleProofTrackingMapper.xml
@@ -183,7 +183,9 @@
WHERE site = #{site}
AND bu_no = #{buNo}
AND UPPER(LTRIM(RTRIM(project_no))) = UPPER(#{projectNo})
- ORDER BY id DESC
+ ORDER BY
+ CASE WHEN id < 100000 THEN 0 ELSE 1 END,
+ id DESC
+
+ INSERT INTO plm_project_info_tracking
+ (id, site, bu_no, project_no, ori_project_id, project_category, project_name, project_desc, status, project_source,
+ customer_no, final_customer_id, priority, customer_remark, remark, need_date, parent_project_no, c_project_region,
+ project_manager, project_owner, engineer, c_quality_engineer1, c_quality_engineer2, c_quality_engineer3,
+ c_quality_engineer4, c_quality_engineer5, c_quality_engineer6, c_manufacture_engineer, doc_engineer,
+ ipqc_hard_tag, c_quality_engineer7, doc_engineer2, project_creation_date, project_close_date,
+ create_date, create_by, update_date, update_by)
+ SELECT
+ p.id,
+ p.site,
+ p.bu_no,
+ p.project_no,
+ p.ori_project_id,
+ p.project_category,
+ p.project_name,
+ p.project_desc,
+ ISNULL(p.status, N'草稿'),
+ p.project_source,
+ p.customer_no,
+ p.final_customer_id,
+ p.priority,
+ '',
+ p.remark,
+ p.need_date,
+ '',
+ p.c_project_region,
+ p.project_manager,
+ p.project_owner,
+ p.engineer,
+ p.c_quality_engineer1,
+ p.c_quality_engineer2,
+ p.c_quality_engineer3,
+ p.c_quality_engineer4,
+ p.c_quality_engineer5,
+ p.c_quality_engineer6,
+ p.c_manufacture_engineer,
+ p.doc_engineer,
+ p.ipqc_hard_tag,
+ p.c_quality_engineer7,
+ p.doc_engineer2,
+ p.project_creation_date,
+ p.project_close_date,
+ GETDATE(),
+ #{operateUser},
+ GETDATE(),
+ #{operateUser}
+ FROM plm_project_info p
+ WHERE p.id = #{projectId}
+ AND NOT EXISTS (
+ SELECT 1
+ FROM plm_project_info_tracking t
+ WHERE t.id = p.id
+ )
+
+
+
+ INSERT INTO plm_project_part_tracking
+ (id, site, project_id, test_part_no, part_type, dry_wet_type, part_desc, part_spec, material_number, final_part_desc,
+ create_date, create_by, update_date, update_by, part_name, final_part_no, base_no, rev_no,
+ project_manager, project_owner, build_date, c_quality_engineer1, c_quality_engineer2, c_quality_engineer3,
+ c_manufacture_engineer, close_date, engineer, status, priority, project_category, need_date, doc_engineer,
+ c_quality_engineer4, c_quality_engineer5, c_quality_engineer6, customer_no, ipqc_hard_tag, c_quality_engineer7, doc_engineer2, remark)
+ SELECT
+ pp.id,
+ pp.site,
+ pp.project_id,
+ pp.test_part_no,
+ pp.part_type,
+ pp.dry_wet_type,
+ pp.part_desc,
+ pp.part_spec,
+ pp.material_number,
+ pp.final_part_desc,
+ GETDATE(),
+ #{operateUser},
+ GETDATE(),
+ #{operateUser},
+ pp.part_name,
+ pp.final_part_no,
+ pp.base_no,
+ pp.rev_no,
+ pp.project_manager,
+ pp.project_owner,
+ pp.build_date,
+ pp.c_quality_engineer1,
+ pp.c_quality_engineer2,
+ pp.c_quality_engineer3,
+ pp.c_manufacture_engineer,
+ pp.close_date,
+ pp.engineer,
+ ISNULL(pp.status, N'草稿'),
+ pp.priority,
+ pp.project_category,
+ pp.need_date,
+ pp.doc_engineer,
+ pp.c_quality_engineer4,
+ pp.c_quality_engineer5,
+ pp.c_quality_engineer6,
+ pp.customer_no,
+ pp.ipqc_hard_tag,
+ pp.c_quality_engineer7,
+ pp.doc_engineer2,
+ pp.remark
+ FROM plm_project_part pp
+ WHERE pp.project_id = #{projectId}
+ AND pp.id = #{projectPartId}
+ AND NOT EXISTS (
+ SELECT 1
+ FROM plm_project_part_tracking t
+ WHERE t.id = pp.id
+ )
+
+
SELECT ISNULL(MAX(CASE WHEN id >= 100000 THEN id END), 100000) + 1
@@ -237,8 +355,14 @@
- SELECT ISNULL(MAX(CASE WHEN id >= 100000 THEN id END), 100000) + 1
- FROM plm_proofing_information_tracking WITH (TABLOCKX, HOLDLOCK)
+ SELECT CASE
+ WHEN #{trackingId,jdbcType=INTEGER} IS NOT NULL AND #{trackingId,jdbcType=INTEGER} > 0
+ THEN #{trackingId,jdbcType=INTEGER}
+ ELSE (
+ SELECT ISNULL(MAX(CASE WHEN id >= 100000 THEN id END), 100000) + 1
+ FROM plm_proofing_information_tracking WITH (TABLOCKX, HOLDLOCK)
+ )
+ END
INSERT INTO plm_proofing_information_tracking
(id, site, project_id, project_part_id, proofing_id, proofing_no, proofing_status, project_phase, project_category,