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,