diff --git a/docs/spec-match-ddl.sql b/docs/spec-match-ddl.sql new file mode 100644 index 00000000..f154146d --- /dev/null +++ b/docs/spec-match-ddl.sql @@ -0,0 +1,16 @@ +-- 规程版本新增匹配条件字段 +ALTER TABLE wms_process_spec_version + ADD COLUMN match_entry_thick_min DECIMAL(8,3) NULL COMMENT '来料厚度下限(mm)', + ADD COLUMN match_entry_thick_max DECIMAL(8,3) NULL COMMENT '来料厚度上限(mm)', + ADD COLUMN match_exit_thick_min DECIMAL(8,3) NULL COMMENT '出口厚度下限(mm)', + ADD COLUMN match_exit_thick_max DECIMAL(8,3) NULL COMMENT '出口厚度上限(mm)', + ADD COLUMN match_entry_width_min DECIMAL(8,2) NULL COMMENT '来料宽度下限(mm)', + ADD COLUMN match_entry_width_max DECIMAL(8,2) NULL COMMENT '来料宽度上限(mm)', + ADD COLUMN match_exit_width_min DECIMAL(8,2) NULL COMMENT '出口宽度下限(mm)', + ADD COLUMN match_exit_width_max DECIMAL(8,2) NULL COMMENT '出口宽度上限(mm)', + ADD COLUMN match_steel_grade VARCHAR(100) NULL COMMENT '钢种关键字(模糊匹配)'; + +-- 钢卷主表新增规程绑定字段 +ALTER TABLE wms_material_coil + ADD COLUMN spec_id BIGINT NULL COMMENT '绑定的规程ID', + ADD COLUMN version_id BIGINT NULL COMMENT '绑定的规程版本ID'; diff --git a/docs/spec-sample-data.sql b/docs/spec-sample-data.sql new file mode 100644 index 00000000..1f2e3ae5 --- /dev/null +++ b/docs/spec-sample-data.sql @@ -0,0 +1,128 @@ +-- ============================================================ +-- 规程匹配测试样本数据 +-- 执行前请确认:SELECT spec_id, spec_code FROM wms_process_spec; +-- SELECT * FROM wms_production_line WHERE line_id = 1; +-- ============================================================ + +-- 先查看现有规程,避免重复 +-- SELECT * FROM wms_process_spec WHERE line_id = 1; +-- SELECT * FROM wms_process_spec_version WHERE is_active = 1; + +-- ───────────────────────────────────────────────── +-- 规程1:酸轧通用规程(宽范围覆盖大多数钢卷) +-- ───────────────────────────────────────────────── +INSERT INTO wms_process_spec ( + spec_code, spec_name, spec_type, line_id, + product_type, is_enabled, del_flag, + remark, create_by, create_time, update_by, update_time +) VALUES ( + 'ACL-STD-001', '酸轧通用规程', 'PROCESS', 1, + 'CR', 1, 0, + '通用匹配规程,覆盖常规酸轧来料范围', 'admin', NOW(), 'admin', NOW() +); + +-- 获取刚插入的 spec_id +SET @spec_id_std = LAST_INSERT_ID(); + +-- 版本V1(生效版本,宽范围) +INSERT INTO wms_process_spec_version ( + spec_id, version_code, is_active, status, del_flag, + match_entry_thick_min, match_entry_thick_max, + match_exit_thick_min, match_exit_thick_max, + match_entry_width_min, match_entry_width_max, + match_exit_width_min, match_exit_width_max, + match_steel_grade, + remark, create_by, create_time, update_by, update_time +) VALUES ( + @spec_id_std, 'V1.0', 1, 1, 0, + 1.500, 8.000, -- 来料厚度范围 mm(热卷厚度典型值 2~6mm,留余量) + 0.200, 4.000, -- 出口厚度范围 mm(冷轧成品典型值 0.3~2mm) + 700.00, 1700.00, -- 来料宽度范围 mm + 700.00, 1700.00, -- 出口宽度范围 mm + NULL, -- 钢种为空 = 不限钢种 + '通用版本,宽范围覆盖', 'admin', NOW(), 'admin', NOW() +); + +-- ───────────────────────────────────────────────── +-- 规程2:酸轧薄规格规程(出口厚度 ≤ 1.0mm) +-- ───────────────────────────────────────────────── +INSERT INTO wms_process_spec ( + spec_code, spec_name, spec_type, line_id, + product_type, is_enabled, del_flag, + remark, create_by, create_time, update_by, update_time +) VALUES ( + 'ACL-THIN-001', '酸轧薄规格规程', 'PROCESS', 1, + 'CR', 1, 0, + '薄规格产品专用,出口厚度不超过1.0mm', 'admin', NOW(), 'admin', NOW() +); + +SET @spec_id_thin = LAST_INSERT_ID(); + +-- 版本V1(生效版本,出口厚度 ≤ 1.0mm) +INSERT INTO wms_process_spec_version ( + spec_id, version_code, is_active, status, del_flag, + match_entry_thick_min, match_entry_thick_max, + match_exit_thick_min, match_exit_thick_max, + match_entry_width_min, match_entry_width_max, + match_exit_width_min, match_exit_width_max, + match_steel_grade, + remark, create_by, create_time, update_by, update_time +) VALUES ( + @spec_id_thin, 'V1.0', 1, 1, 0, + 2.000, 5.000, -- 来料厚度 + 0.200, 1.000, -- 出口厚度(薄规格) + 800.00, 1500.00, -- 来料宽度 + 800.00, 1500.00, -- 出口宽度 + NULL, -- 不限钢种 + '薄规格专用版本', 'admin', NOW(), 'admin', NOW() +); + +-- ───────────────────────────────────────────────── +-- 规程3:高强钢规程(含钢种匹配) +-- ───────────────────────────────────────────────── +INSERT INTO wms_process_spec ( + spec_code, spec_name, spec_type, line_id, + product_type, is_enabled, del_flag, + remark, create_by, create_time, update_by, update_time +) VALUES ( + 'ACL-HSS-001', '酸轧高强钢规程', 'PROCESS', 1, + 'CR', 1, 0, + '高强度结构钢专用规程', 'admin', NOW(), 'admin', NOW() +); + +SET @spec_id_hss = LAST_INSERT_ID(); + +INSERT INTO wms_process_spec_version ( + spec_id, version_code, is_active, status, del_flag, + match_entry_thick_min, match_entry_thick_max, + match_exit_thick_min, match_exit_thick_max, + match_entry_width_min, match_entry_width_max, + match_exit_width_min, match_exit_width_max, + match_steel_grade, + remark, create_by, create_time, update_by, update_time +) VALUES ( + @spec_id_hss, 'V1.0', 1, 1, 0, + 2.500, 7.000, + 0.500, 2.500, + 900.00, 1600.00, + 900.00, 1600.00, + 'Q', -- 匹配钢种含 "Q" 的(Q235、Q345、Q420 等) + '高强钢版本,钢种含Q', 'admin', NOW(), 'admin', NOW() +); + +-- ───────────────────────────────────────────────── +-- 验证查询 +-- ───────────────────────────────────────────────── +SELECT + s.spec_id, s.spec_code, s.spec_name, s.line_id, + v.version_id, v.version_code, v.is_active, + v.match_entry_thick_min, v.match_entry_thick_max, + v.match_exit_thick_min, v.match_exit_thick_max, + v.match_entry_width_min, v.match_entry_width_max, + v.match_steel_grade +FROM wms_process_spec s +JOIN wms_process_spec_version v ON s.spec_id = v.spec_id +WHERE s.line_id = 1 + AND s.del_flag = 0 + AND v.del_flag = 0 +ORDER BY s.spec_id, v.version_code; diff --git a/klp-admin/src/main/java/com/klp/framework/client/SqlServerApiClient.java b/klp-admin/src/main/java/com/klp/framework/client/SqlServerApiClient.java index 1bdfd602..9d585618 100644 --- a/klp-admin/src/main/java/com/klp/framework/client/SqlServerApiClient.java +++ b/klp-admin/src/main/java/com/klp/framework/client/SqlServerApiClient.java @@ -361,6 +361,59 @@ public class SqlServerApiClient { ); } + public ExecuteSqlResponse queryPlanListByHotCoilIdLike(String hotCoilId, int page, int pageSize) { + int endRow = page * pageSize; + int startRow = endRow - pageSize; + Map params = new java.util.HashMap<>(); + params.put("startRow", startRow); + params.put("endRow", endRow); + params.put("hotCoilId", "%" + hotCoilId + "%"); + return executeSql( + "oracle", + "select * from (select t.*, ROWNUM rn from (select * from JXPLTCM.PLTCM_PDI_PLAN where HOT_COILID LIKE :hotCoilId order by INSDATE desc) t where ROWNUM <= :endRow) where rn > :startRow", + params + ); + } + + public ExecuteSqlResponse queryPlanCountByHotCoilIdLike(String hotCoilId) { + return executeSql( + "oracle", + "select count(*) as total from JXPLTCM.PLTCM_PDI_PLAN where HOT_COILID LIKE :hotCoilId", + singletonParam("hotCoilId", "%" + hotCoilId + "%") + ); + } + + /** + * 批量按 EXCOILID 查询出口卷上线/下线时间(PLTCM_PDO_EXCOIL) + */ + public ExecuteSqlResponse queryExcoilTimesByCoilIds(List coilIds) { + if (coilIds == null || coilIds.isEmpty()) { + return new ExecuteSqlResponse(); + } + String inList = coilIds.stream() + .filter(id -> id != null && !id.trim().isEmpty()) + .map(id -> "'" + id.replace("'", "''") + "'") + .collect(java.util.stream.Collectors.joining(", ")); + if (inList.isEmpty()) return new ExecuteSqlResponse(); + String sql = "SELECT EXCOILID, START_DATE, END_DATE FROM JXPLTCM.PLTCM_PDO_EXCOIL WHERE EXCOILID IN (" + inList + ")"; + return executeSql("oracle", sql, emptyParams()); + } + + /** + * 批量按 HOT_COILID 查询计划数据(IN 子句,一次 Oracle 调用获取整页数据) + */ + public ExecuteSqlResponse queryPlanDimsByHotCoilIds(List hotCoilIds) { + if (hotCoilIds == null || hotCoilIds.isEmpty()) { + return new ExecuteSqlResponse(); + } + String inList = hotCoilIds.stream() + .map(id -> "'" + id.replace("'", "''") + "'") + .collect(java.util.stream.Collectors.joining(", ")); + String sql = "SELECT HOT_COILID, ENTRY_THICK, EXIT_THICK, ENTRY_WIDTH, EXIT_WIDTH, GRADE, PROCESS_CODE, COILID " + + "FROM JXPLTCM.PLTCM_PDI_PLAN WHERE HOT_COILID IN (" + inList + ")"; + return executeSql("oracle", sql, emptyParams()); + } + public ExecuteSqlResponse queryProSegByEncoilId(String encoilId) { return executeSql( "oracle", diff --git a/klp-admin/src/main/java/com/klp/framework/service/SqlServerApiBusinessService.java b/klp-admin/src/main/java/com/klp/framework/service/SqlServerApiBusinessService.java index 416eb7ce..afd11e7f 100644 --- a/klp-admin/src/main/java/com/klp/framework/service/SqlServerApiBusinessService.java +++ b/klp-admin/src/main/java/com/klp/framework/service/SqlServerApiBusinessService.java @@ -66,6 +66,51 @@ public class SqlServerApiBusinessService { return PlanListView.fromExecuteSqlResponse(client.queryPlanListByStatus(status)); } + public PlanListView getPlanListByHotCoilIdLike(String hotCoilId, int page, int pageSize) { + return PlanListView.fromExecuteSqlResponse(client.queryPlanListByHotCoilIdLike(hotCoilId, page, pageSize)); + } + + public long getPlanCountByHotCoilIdLike(String hotCoilId) { + List> rows = asRowList(client.queryPlanCountByHotCoilIdLike(hotCoilId)); + if (rows.isEmpty()) return 0L; + Object total = rows.get(0).get("total"); + Number n = asNumber(total); + return n == null ? 0L : n.longValue(); + } + + /** + * 批量获取 L2 计划维度数据(来回料厚/宽、钢种),一次 Oracle 调用覆盖整页。 + * 返回 Map<hotCoilId, firstRow>,同一 HOT_COILID 只保留第一行。 + */ + /** + * 批量查询出口卷上线/下线时间,返回 Map<excoilId, {START_DATE, END_DATE}> + */ + public Map> getExcoilTimesByCoilIds(List coilIds) { + if (coilIds == null || coilIds.isEmpty()) return Collections.emptyMap(); + List> rows = asRowList(client.queryExcoilTimesByCoilIds(coilIds)); + Map> result = new LinkedHashMap<>(); + for (Map row : rows) { + Object id = row.getOrDefault("EXCOILID", row.get("excoilid")); + if (id != null) result.putIfAbsent(id.toString().trim(), row); + } + return result; + } + + public Map> getPlanDimsByHotCoilIds(List hotCoilIds) { + if (hotCoilIds == null || hotCoilIds.isEmpty()) { + return Collections.emptyMap(); + } + List> rows = asRowList(client.queryPlanDimsByHotCoilIds(hotCoilIds)); + Map> result = new LinkedHashMap<>(); + for (Map row : rows) { + Object hcId = row.getOrDefault("HOT_COILID", row.get("hot_coilid")); + if (hcId != null) { + result.putIfAbsent(hcId.toString().trim(), row); + } + } + return result; + } + /** * 计划详情:按成品卷号查询单条计划。 */ diff --git a/klp-admin/src/main/java/com/klp/wms/WmsSpecSyncController.java b/klp-admin/src/main/java/com/klp/wms/WmsSpecSyncController.java new file mode 100644 index 00000000..75e246f5 --- /dev/null +++ b/klp-admin/src/main/java/com/klp/wms/WmsSpecSyncController.java @@ -0,0 +1,502 @@ +package com.klp.wms; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.R; +import com.klp.domain.WmsCoilPendingAction; +import com.klp.domain.WmsProductionLine; +import com.klp.domain.bo.WmsMaterialCoilBo; +import com.klp.domain.vo.WmsCoilPendingActionVo; +import com.klp.domain.vo.WmsMaterialCoilVo; +import com.klp.domain.vo.WmsProcessSpecVersionVo; +import com.klp.framework.service.SqlServerApiBusinessService; +import com.klp.mapper.WmsCoilPendingActionMapper; +import com.klp.mapper.WmsProductionLineMapper; +import com.klp.service.IWmsMaterialCoilService; +import com.klp.service.IWmsProcessSpecVersionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 规程同步接口(跨模块:同时依赖 klp-admin 的 L2 服务和 klp-wms 的规程/钢卷服务) + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/wms/specSync") +public class WmsSpecSyncController extends BaseController { + + private final SqlServerApiBusinessService sqlServerApiBusinessService; + private final IWmsProcessSpecVersionService specVersionService; + private final IWmsMaterialCoilService materialCoilService; + private final WmsProductionLineMapper productionLineMapper; + private final WmsCoilPendingActionMapper pendingActionMapper; + + /** + * 根据热卷号匹配最佳规程版本(供 typing.vue 在 L2 填入后调用) + */ + @GetMapping("/matchBest") + public R matchBest(@RequestParam String hotCoilId, + @RequestParam(required = false) Long lineId) { + SqlServerApiBusinessService.PlanDetailView plan = + sqlServerApiBusinessService.getPlanByHotCoilId(hotCoilId); + if (plan == null || plan.getFirstRow().isEmpty()) { + return R.ok(null); + } + Map row = plan.getFirstRow(); + BigDecimal entryThick = toBigDecimal(row, "ENTRY_THICK", "entry_thick"); + BigDecimal exitThick = toBigDecimal(row, "EXIT_THICK", "exit_thick"); + BigDecimal entryWidth = toBigDecimal(row, "ENTRY_WIDTH", "entry_width"); + BigDecimal exitWidth = toBigDecimal(row, "EXIT_WIDTH", "exit_width"); + String grade = toStr(row, "GRADE", "grade"); + + WmsProcessSpecVersionVo best = specVersionService.matchBestVersion( + entryThick, exitThick, entryWidth, exitWidth, grade, lineId); + return R.ok(best); + } + + /** + * 规程同步分页列表。 + * 以 L3(WMS MySQL)为主维度:只展示在 wms_coil_pending_action.processed_coil_ids + * 中出现过的钢卷(即生产后处理过的),再按 enter_coil_no = HOT_COILID 从 L2(Oracle) + * 批量富化计划维度(入口/出口厚宽、钢种等)及上线/下线时间。 + */ + @GetMapping("/pageList") + public R> pageList( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "40") int pageSize, + @RequestParam(required = false) String enterCoilNo, + @RequestParam(required = false) String currentCoilNo, + @RequestParam(required = false) String material, + @RequestParam(required = false) String qualityStatus, + @RequestParam(required = false) String specCode, + @RequestParam(required = false) String syncStatus, + @RequestParam(required = false) Long lineId) { + + // ── 1. 预加载规程版本 ────────────────────────────────────────────────── + // 活跃版本:仅用于推荐候选匹配 + List allActive = specVersionService.queryActiveVersionsEnriched(null); + List globalCandidates = lineId != null + ? allActive.stream().filter(v -> lineId.equals(v.getLineId())).collect(Collectors.toList()) + : allActive; + // 全部版本(含历史版本):用于展示已绑定规程名称,避免旧版本查不到 + Map allVersionById = specVersionService.queryAllVersionsEnriched().stream() + .collect(Collectors.toMap(WmsProcessSpecVersionVo::getVersionId, v -> v, (a, b) -> a)); + + // specCode 过滤 → specId 集合(后置内存过滤,从全部版本里搜) + final Set filterSpecIds; + if (StringUtils.hasText(specCode)) { + final String kw = specCode.trim().toLowerCase(); + filterSpecIds = allVersionById.values().stream() + .filter(v -> (v.getSpecCode() != null && v.getSpecCode().toLowerCase().contains(kw)) + || (v.getSpecName() != null && v.getSpecName().toLowerCase().contains(kw))) + .map(WmsProcessSpecVersionVo::getSpecId) + .collect(Collectors.toSet()); + } else { + filterSpecIds = null; + } + + // ── 2. 预加载产线表 → actionType(int) → Set ─────────────────── + // 有效产线:wms_production_line.action_type IS NOT NULL(即配置了操作类型的产线) + List allLines = productionLineMapper.selectList( + Wrappers.lambdaQuery() + .isNotNull(WmsProductionLine::getActionType)); + Map> actionTypeToLineIds = new HashMap<>(); + for (WmsProductionLine pl : allLines) { + if (!StringUtils.hasText(pl.getActionType())) continue; + for (String part : pl.getActionType().split(",")) { + try { + int at = Integer.parseInt(part.trim()); + actionTypeToLineIds.computeIfAbsent(at, k -> new HashSet<>()).add(pl.getLineId()); + } catch (NumberFormatException ignore) { /* 跳过非数字 */ } + } + } + + // ── 3. 展开已录入(action_status=2)待操作的 processed_coil_ids → 输出钢卷 ID ── + Set validActionTypes = actionTypeToLineIds.keySet(); + Set typedCoilIds = new LinkedHashSet<>(); + Map allCoilIdToActionType = new HashMap<>(); + + if (!validActionTypes.isEmpty()) { + List allPaList = + pendingActionMapper.selectAllProcessedCoilIdsAndActionStatus(); + for (WmsCoilPendingAction pa : allPaList) { + if (pa.getActionType() == null || !validActionTypes.contains(pa.getActionType())) continue; + if (!StringUtils.hasText(pa.getProcessedCoilIds())) continue; + for (String idStr : pa.getProcessedCoilIds().split(",")) { + try { + long pid = Long.parseLong(idStr.trim()); + typedCoilIds.add(pid); + allCoilIdToActionType.putIfAbsent(pid, pa.getActionType()); + } catch (NumberFormatException ignore) { /* 跳过非数字 */ } + } + } + } + final Map coilIdToActionType = allCoilIdToActionType; + + // ── 4. 计数分页(仅已录入钢卷,按规程绑定状态过滤)──────────────────────── + final String effectiveSyncStatus; + if ("synced".equals(syncStatus)) effectiveSyncStatus = "synced"; + else if ("unsynced".equals(syncStatus)) effectiveSyncStatus = "unsynced"; + else effectiveSyncStatus = null; + + if (typedCoilIds.isEmpty()) { + Map emptyResult = new LinkedHashMap<>(); + emptyResult.put("rows", Collections.emptyList()); + emptyResult.put("total", 0L); + return R.ok(emptyResult); + } + + // ── 4a. 整体汇总统计(忽略 syncStatus 过滤,体现全量分布) ───────────────── + Map overallStats = materialCoilService.getOverallSyncStats( + typedCoilIds, enterCoilNo, currentCoilNo, material, qualityStatus, filterSpecIds); + + int offset = (pageNum - 1) * pageSize; + long total = materialCoilService.countByProcessedCoilIds( + typedCoilIds, enterCoilNo, currentCoilNo, material, qualityStatus, + effectiveSyncStatus, filterSpecIds); + List l3Coils = materialCoilService.queryByProcessedCoilIds( + typedCoilIds, enterCoilNo, currentCoilNo, material, qualityStatus, + effectiveSyncStatus, filterSpecIds, offset, pageSize); + + if (l3Coils.isEmpty()) { + Map emptyResult = new LinkedHashMap<>(); + emptyResult.put("rows", Collections.emptyList()); + emptyResult.put("total", total); + emptyResult.put("overallStats", overallStats); + return R.ok(emptyResult); + } + + // ── 5. L2 富化:批量按 enter_coil_no(= HOT_COILID)查询计划维度 ────────── + List enterCoilNos = new ArrayList<>(); + Set seenNos = new HashSet<>(); + for (WmsMaterialCoilVo c : l3Coils) { + String primary = primaryEnterCoilNo(c.getEnterCoilNo()); + if (primary != null && seenNos.add(primary)) enterCoilNos.add(primary); + } + Map> l2DimMap = + sqlServerApiBusinessService.getPlanDimsByHotCoilIds(enterCoilNos); + + // ── 6. 上线/下线时间:PLTCM_PDO_EXCOIL.EXCOILID = PLTCM_PDI_PLAN.COILID ── + List l2CoilIds = new ArrayList<>(); + for (Map dims : l2DimMap.values()) { + String cid = toStr(dims, "COILID", "coilid"); + if (StringUtils.hasText(cid)) l2CoilIds.add(cid); + } + Map> excoilTimeMap = + sqlServerApiBusinessService.getExcoilTimesByCoilIds(l2CoilIds); + + // ── 7. 组装行 ────────────────────────────────────────────────────────────── + List> rows = new ArrayList<>(); + for (WmsMaterialCoilVo coil : l3Coils) { + Map row = new LinkedHashMap<>(); + String primaryNo = primaryEnterCoilNo(coil.getEnterCoilNo()); + + // ── L3 基础字段 ── + row.put("wmsCoilId", coil.getCoilId()); + row.put("enterCoilNo", primaryNo); + row.put("currentCoilNo", coil.getCurrentCoilNo()); + row.put("supplierCoilNo", coil.getSupplierCoilNo()); + row.put("netWeight", coil.getNetWeight()); + row.put("actualThickness", coil.getActualThickness()); + row.put("actualWidth", coil.getActualWidth()); + row.put("qualityStatus", coil.getQualityStatus()); + row.put("specification", coil.getSpecification()); + row.put("material", coil.getMaterial()); + + // 是否已流转(data_type=0) + boolean movedOn = Integer.valueOf(0).equals(coil.getDataType()); + row.put("movedOn", movedOn); + row.put("dataType", coil.getDataType()); + + // ── L2 数据有则富化,无则留空(不跳过,避免破坏分页偏移)── + Map l2row = primaryNo != null ? l2DimMap.get(primaryNo) : null; + BigDecimal entryThick = null, exitThick = null, entryWidth = null, exitWidth = null; + String grade = null, l2CoilId = null; + if (l2row != null) { + entryThick = toBigDecimal(l2row, "ENTRY_THICK", "entry_thick"); + exitThick = toBigDecimal(l2row, "EXIT_THICK", "exit_thick"); + entryWidth = toBigDecimal(l2row, "ENTRY_WIDTH", "entry_width"); + exitWidth = toBigDecimal(l2row, "EXIT_WIDTH", "exit_width"); + grade = toStr(l2row, "GRADE", "grade"); + l2CoilId = toStr(l2row, "COILID", "coilid"); + row.put("entryThick", entryThick); + row.put("exitThick", exitThick); + row.put("entryWidth", entryWidth); + row.put("exitWidth", exitWidth); + row.put("grade", grade); + row.put("processCode", toStr(l2row, "PROCESS_CODE", "process_code")); + row.put("l2CoilId", l2CoilId); + row.put("l2Found", true); + } + + // 上线/下线时间 + Map excoilTimes = StringUtils.hasText(l2CoilId) + ? excoilTimeMap.get(l2CoilId) : null; + if (excoilTimes != null) { + row.put("onlineTime", excoilTimes.getOrDefault("START_DATE", excoilTimes.get("start_date"))); + row.put("offlineTime", excoilTimes.getOrDefault("END_DATE", excoilTimes.get("end_date"))); + } + + // ── 规程绑定状态 ── + if (coil.getVersionId() != null) { + WmsProcessSpecVersionVo bound = allVersionById.get(coil.getVersionId()); + if (bound != null) { + row.put("specCode", bound.getSpecCode()); + row.put("specName", bound.getSpecName()); + row.put("versionCode", bound.getVersionCode()); + putMatchConds(row, bound); + } + row.put("syncStatus", "synced"); + } else { + row.put("syncStatus", "unsynced"); + // 按产线限定候选规程(lineId=null 的规程不限定产线,始终纳入) + Integer pendingAt = coilIdToActionType.get(coil.getCoilId()); + Set allowedLineIds = pendingAt != null + ? actionTypeToLineIds.getOrDefault(pendingAt, Collections.emptySet()) + : null; + // 优先用同产线规程;若过滤后为空则兜底到全量活跃版本 + List candidates; + if (allowedLineIds == null || allowedLineIds.isEmpty()) { + candidates = globalCandidates; + } else { + candidates = globalCandidates.stream() + .filter(v -> v.getLineId() == null || allowedLineIds.contains(v.getLineId())) + .collect(Collectors.toList()); + if (candidates.isEmpty()) { + candidates = globalCandidates; // 兜底:产线无匹配规程时展示全部 + } + } + WmsProcessSpecVersionVo best = null; + int bestScore = -1; + for (WmsProcessSpecVersionVo v : candidates) { + int s = scoreVersion(v, entryThick, exitThick, entryWidth, exitWidth, grade); + if (s > bestScore) { bestScore = s; best = v; } + } + // bestScore >= 0:只要存在候选规程就展示推荐(0 = 无维度条件匹配但仍有版本可绑) + if (best != null && bestScore >= 0) { + row.put("candidateSpecCode", best.getSpecCode()); + row.put("candidateSpecName", best.getSpecName()); + row.put("candidateVersionCode", best.getVersionCode()); + row.put("candidateVersionId", best.getVersionId()); + row.put("candidateSpecId", best.getSpecId()); + putMatchConds(row, best); + } + } + rows.add(row); + } + + Map result = new LinkedHashMap<>(); + result.put("rows", rows); + result.put("total", total); + result.put("overallStats", overallStats); + return R.ok(result); + } + + // ── 私有工具方法 ───────────────────────────────────────────────────────────── + + /** + * enter_coil_no 字段可能存储了多个逗号分隔的热卷号(如 "26032550,26032550")。 + * 取第一个非空值作为主键,用于展示和 L2 关联查询。 + */ + private String primaryEnterCoilNo(String raw) { + if (!StringUtils.hasText(raw)) return null; + String first = raw.split(",")[0].trim(); + return first.isEmpty() ? null : first; + } + + private int scoreVersion(WmsProcessSpecVersionVo v, + BigDecimal entryThick, BigDecimal exitThick, + BigDecimal entryWidth, BigDecimal exitWidth, String grade) { + int score = 0; + if (entryThick != null && v.getMatchEntryThickMin() != null && v.getMatchEntryThickMax() != null + && entryThick.compareTo(v.getMatchEntryThickMin()) >= 0 + && entryThick.compareTo(v.getMatchEntryThickMax()) <= 0) score++; + if (exitThick != null && v.getMatchExitThickMin() != null && v.getMatchExitThickMax() != null + && exitThick.compareTo(v.getMatchExitThickMin()) >= 0 + && exitThick.compareTo(v.getMatchExitThickMax()) <= 0) score++; + if (entryWidth != null && v.getMatchEntryWidthMin() != null && v.getMatchEntryWidthMax() != null + && entryWidth.compareTo(v.getMatchEntryWidthMin()) >= 0 + && entryWidth.compareTo(v.getMatchEntryWidthMax()) <= 0) score++; + if (exitWidth != null && v.getMatchExitWidthMin() != null && v.getMatchExitWidthMax() != null + && exitWidth.compareTo(v.getMatchExitWidthMin()) >= 0 + && exitWidth.compareTo(v.getMatchExitWidthMax()) <= 0) score++; + if (StringUtils.hasText(grade) && StringUtils.hasText(v.getMatchSteelGrade()) + && grade.toLowerCase().contains(v.getMatchSteelGrade().toLowerCase())) score++; + return score; + } + + private void putMatchConds(Map row, WmsProcessSpecVersionVo v) { + row.put("matchEntryThickMin", v.getMatchEntryThickMin()); + row.put("matchEntryThickMax", v.getMatchEntryThickMax()); + row.put("matchExitThickMin", v.getMatchExitThickMin()); + row.put("matchExitThickMax", v.getMatchExitThickMax()); + row.put("matchEntryWidthMin", v.getMatchEntryWidthMin()); + row.put("matchEntryWidthMax", v.getMatchEntryWidthMax()); + row.put("matchExitWidthMin", v.getMatchExitWidthMin()); + row.put("matchExitWidthMax", v.getMatchExitWidthMax()); + row.put("matchSteelGrade", v.getMatchSteelGrade()); + } + + /** + * 待录入核查分页列表。 + * 数据源:wms_production_line 有效产线对应的待操作记录中 action_status != 2(尚未完成 typing)的条目。 + * 额外按入场卷号从 L2(Oracle)查询计划/实绩维度,供核查人员判断二级是否已有实绩。 + */ + @GetMapping("/untypedPageList") + public R> untypedPageList( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "40") int pageSize, + @RequestParam(required = false) String enterCoilNo, + @RequestParam(required = false) String currentCoilNo, + @RequestParam(required = false) String operatorName) { + + // ── 1. 有效产线 actionType ────────────────────────────────────────────── + List lines = productionLineMapper.selectList( + Wrappers.lambdaQuery().isNotNull(WmsProductionLine::getActionType)); + Set validAts = new HashSet<>(); + for (WmsProductionLine pl : lines) { + if (!StringUtils.hasText(pl.getActionType())) continue; + for (String part : pl.getActionType().split(",")) { + try { validAts.add(Integer.parseInt(part.trim())); } catch (NumberFormatException ignore) {} + } + } + if (validAts.isEmpty()) { + Map empty = new LinkedHashMap<>(); + empty.put("rows", Collections.emptyList()); + empty.put("total", 0L); + return R.ok(empty); + } + + // ── 2. 分页查询未录入待操作 ──────────────────────────────────────────────── + // 条件1:action_status IN (0,1) —— 0=待处理/1=进行中,2=已完成/3=已取消均排除 + // 条件2:processed_coil_ids 为空 —— 只有未写入产出卷 ID 的记录才是尚未录入的 + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("wcpa.del_flag", 0); + qw.in("wcpa.action_type", validAts); + qw.in("wcpa.action_status", java.util.Arrays.asList(0, 1)); + qw.and(w -> w.isNull("wcpa.processed_coil_ids") + .or().eq("wcpa.processed_coil_ids", "")); + if (StringUtils.hasText(enterCoilNo)) qw.like("wmc.enter_coil_no", enterCoilNo); + if (StringUtils.hasText(currentCoilNo)) qw.like("wcpa.current_coil_no", currentCoilNo); + if (StringUtils.hasText(operatorName)) qw.like("wcpa.operator_name", operatorName); + qw.orderByDesc("wcpa.scan_time"); + + Page voPage = + pendingActionMapper.selectVoPagePlus(Page.of(pageNum, pageSize), qw); + List paList = voPage.getRecords(); + + if (paList.isEmpty()) { + Map empty = new LinkedHashMap<>(); + empty.put("rows", Collections.emptyList()); + empty.put("total", voPage.getTotal()); + return R.ok(empty); + } + + // ── 3. L2 富化 ───────────────────────────────────────────────────────── + List nos = new ArrayList<>(); + Set seen = new HashSet<>(); + for (WmsCoilPendingActionVo pa : paList) { + String primary = primaryEnterCoilNo(pa.getEnterCoilNo()); + if (primary != null && seen.add(primary)) nos.add(primary); + } + Map> l2DimMap = nos.isEmpty() + ? Collections.emptyMap() + : sqlServerApiBusinessService.getPlanDimsByHotCoilIds(nos); + + // ── 4. 组装行 ────────────────────────────────────────────────────────── + List> rows = new ArrayList<>(); + for (WmsCoilPendingActionVo pa : paList) { + String primaryNo = primaryEnterCoilNo(pa.getEnterCoilNo()); + Map l2row = primaryNo != null ? l2DimMap.get(primaryNo) : null; + Map row = new LinkedHashMap<>(); + row.put("actionId", pa.getActionId()); + row.put("coilId", pa.getCoilId()); + row.put("actionType", pa.getActionType()); + row.put("actionStatus", pa.getActionStatus()); + row.put("scanTime", pa.getScanTime()); + row.put("createTime", pa.getCreateTime()); + row.put("operatorName", pa.getOperatorName()); + row.put("warehouseName", pa.getWarehouseName()); + row.put("enterCoilNo", primaryNo); + row.put("currentCoilNo", pa.getCurrentCoilNo()); + row.put("supplierCoilNo", pa.getSupplierCoilNo()); + row.put("material", pa.getMaterial()); + row.put("specification", pa.getSpecification()); + row.put("itemName", pa.getItemName()); + if (l2row != null) { + row.put("entryThick", toBigDecimal(l2row, "ENTRY_THICK", "entry_thick")); + row.put("exitThick", toBigDecimal(l2row, "EXIT_THICK", "exit_thick")); + row.put("entryWidth", toBigDecimal(l2row, "ENTRY_WIDTH", "entry_width")); + row.put("exitWidth", toBigDecimal(l2row, "EXIT_WIDTH", "exit_width")); + row.put("grade", toStr(l2row, "GRADE", "grade")); + row.put("processCode", toStr(l2row, "PROCESS_CODE", "process_code")); + row.put("l2Found", true); + } else { + row.put("l2Found", false); + } + rows.add(row); + } + + Map result = new LinkedHashMap<>(); + result.put("rows", rows); + result.put("total", voPage.getTotal()); + return R.ok(result); + } + + /** + * 批量同步规程绑定。 + * 请求体:{ "bindings": [{ "coilId": 123, "specId": 456, "versionId": 789 }, ...] } + * 前端已完成匹配计算,直接写入指定的 specId/versionId,不重新跑匹配算法。 + */ + @PostMapping("/syncSpec") + @SuppressWarnings("unchecked") + public R syncSpec(@RequestBody Map body) { + List> bindings = (List>) body.get("bindings"); + if (bindings == null || bindings.isEmpty()) { + return R.fail("bindings 不能为空"); + } + for (Map b : bindings) { + try { + Long coilId = toLong(b, "coilId"); + Long specId = toLong(b, "specId"); + Long versionId = toLong(b, "versionId"); + if (coilId == 0L || specId == 0L || versionId == 0L) continue; + WmsMaterialCoilBo bo = new WmsMaterialCoilBo(); + bo.setCoilId(coilId); + bo.setSpecId(specId); + bo.setVersionId(versionId); + materialCoilService.updateSimple(bo); + } catch (Exception e) { + log.warn("规程同步失败 binding={}", b, e); + } + } + return R.ok(); + } + + private BigDecimal toBigDecimal(Map row, String upperKey, String lowerKey) { + Object val = row.getOrDefault(upperKey, row.get(lowerKey)); + if (val == null) return null; + try { return new BigDecimal(val.toString()); } catch (NumberFormatException e) { return null; } + } + + private String toStr(Map row, String upperKey, String lowerKey) { + Object val = row.getOrDefault(upperKey, row.get(lowerKey)); + return val == null ? null : val.toString().trim(); + } + + private long toLong(Map row, String key) { + if (row == null) return 0L; + Object v = row.get(key); + if (v == null) return 0L; + try { return Long.parseLong(v.toString()); } catch (NumberFormatException e) { return 0L; } + } +} diff --git a/klp-admin/src/main/resources/application-prod.yml b/klp-admin/src/main/resources/application-prod.yml index f4555360..05aa613c 100644 --- a/klp-admin/src/main/resources/application-prod.yml +++ b/klp-admin/src/main/resources/application-prod.yml @@ -126,9 +126,9 @@ spring: # password: root hikari: # 最大连接池数量 - maxPoolSize: 20 + maxPoolSize: 10 # 最小空闲线程数量 - minIdle: 10 + minIdle: 3 # 配置获取连接等待超时的时间 connectionTimeout: 30000 # 校验超时时间 diff --git a/klp-ui/src/api/wms/processSpecVersion.js b/klp-ui/src/api/wms/processSpecVersion.js index e77f68f5..5da9569f 100644 --- a/klp-ui/src/api/wms/processSpecVersion.js +++ b/klp-ui/src/api/wms/processSpecVersion.js @@ -44,3 +44,36 @@ export function delProcessSpecVersion(versionId) { method: 'delete' }) } + +export function matchBestSpecVersion(hotCoilId, lineId) { + return request({ + url: '/wms/specSync/matchBest', + method: 'get', + params: { hotCoilId, lineId } + }) +} + +// bindings: [{coilId, specId, versionId}, ...] +export function syncCoilSpec(bindings) { + return request({ + url: '/wms/specSync/syncSpec', + method: 'post', + data: { bindings } + }) +} + +export function getCoilSyncPageList(params) { + return request({ + url: '/wms/specSync/pageList', + method: 'get', + params + }) +} + +export function getUntypedPageList(params) { + return request({ + url: '/wms/specSync/untypedPageList', + method: 'get', + params + }) +} diff --git a/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue b/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue index 3416d481..94401507 100644 --- a/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue +++ b/klp-ui/src/views/micro/pages/acid/components/ActualPerformance.vue @@ -198,92 +198,12 @@ 重置 - -
-
规程关联
- - 请先选择钢卷 -
- - -
- - 共 {{ specVersionsForDialog.length }} 条 -
- - - - - - - - - - - -
- 取消 - 确认关联 -
-
@@ -299,12 +219,6 @@ import { getTimingRealtimeData, getPresetSetupByCoilId } from '@/api/l2/timing' -import { listProcessSpecVersion, getProcessSpecVersion } from '@/api/wms/processSpecVersion' -import { listProcessCoilRecord, upsertProcessCoilRecord } from '@/api/wms/processCoilRecord' -import { listProcessSpec } from '@/api/wms/processSpec' -import { listProcessPlan } from '@/api/wms/processPlan' -import { listProcessPlanParam } from '@/api/wms/processPlanParam' -import { batchAddProcessAnomaly } from '@/api/wms/processAnomaly' // 趋势参数树结构,对应 PLTCM_PRO_SEG 列名 const TREND_GROUPS = [ @@ -492,32 +406,6 @@ export default { topTableHeight: 'calc(40vh - 80px)', chartInstances: [], resizeHandler: null, - // ── 规程关联 ── - coilBinding: null, // 当前选中钢卷已有的关联记录 { versionId, versionCode, ... } - coilBindingLoading: false, - specBindDialog: false, - specVersionLoading: false, - specVersionRawList: [], // 所有 wms_process_spec_version - specList: [], // 所有 wms_process_spec(用于显示规程名) - specBindSelectedId: null, // 弹窗内选中的 versionId - specBindShowAll: false, // true=显示全部版本,false=只显示生效版本 - specBindLoading: false - } - }, - computed: { - /** 弹窗中展示的版本列表(含规程名,支持只看生效版本) */ - specVersionsForDialog() { - const specMap = {} - this.specList.forEach(s => { specMap[s.specId] = s }) - let list = this.specVersionRawList.map(v => ({ - ...v, - specCode: specMap[v.specId] ? specMap[v.specId].specCode : '—', - specName: specMap[v.specId] ? specMap[v.specId].specName : '—', - })) - if (!this.specBindShowAll) { - list = list.filter(v => v.isActive === 1) - } - return list } }, created() { @@ -558,9 +446,7 @@ export default { this.shapeRows = null this.presetData = null this.selectedTrendParam = null - this.coilBinding = null this.disposeAllCharts() - this.loadCoilBinding() const encoilId = row.ENCOILID || row.encoilid const excoilId = row.EXCOILID || row.excoilid @@ -979,7 +865,6 @@ export default { this.gaugeRows = null this.shapeRows = null this.selectedTrendParam = null - this.coilBinding = null this.disposeAllCharts() this.pagination.page = 1 this.loadExcoilCount() @@ -997,177 +882,6 @@ export default { this.$refs.qualityReport.open(row, segData, gaugeRows, shapeRows, presetData) }, - // ── 规程关联 ───────────────────────────────────────── - /** 加载当前选中钢卷的已有规程关联记录 */ - async loadCoilBinding() { - if (!this.selectedRow) return - const coilId = this.selectedRow.EXCOILID || this.selectedRow.excoilid - if (!coilId) return - this.coilBindingLoading = true - try { - const res = await listProcessCoilRecord({ coilId, pageNum: 1, pageSize: 1 }) - const rec = (res.rows || [])[0] - if (rec) { - // 补充版本号显示 - try { - const verRes = await getProcessSpecVersion(rec.versionId) - this.coilBinding = { ...rec, versionCode: verRes.data?.versionCode || String(rec.versionId) } - } catch { - this.coilBinding = { ...rec, versionCode: String(rec.versionId) } - } - } else { - this.coilBinding = null - } - } catch { - this.coilBinding = null - } finally { - this.coilBindingLoading = false - } - }, - - /** 打开选择规程版本的弹窗 */ - async openSpecBindDialog() { - this.specBindSelectedId = this.coilBinding ? this.coilBinding.versionId : null - this.specBindShowAll = !this.coilBinding // 若已关联默认显示全部;否则只看生效版本 - this.specBindDialog = true - this.specVersionLoading = true - try { - const [verRes, specRes] = await Promise.all([ - listProcessSpecVersion({ pageNum: 1, pageSize: 500 }), - listProcessSpec({ pageNum: 1, pageSize: 200 }) - ]) - this.specVersionRawList = verRes.rows || [] - this.specList = specRes.rows || [] - } finally { - this.specVersionLoading = false - } - }, - - /** 确认关联,并自动检测 L1 实绩与规程参数限值的偏差,将异常落库 */ - async confirmSpecBind() { - if (!this.specBindSelectedId) return - this.specBindLoading = true - try { - const coilId = this.selectedRow.EXCOILID || this.selectedRow.excoilid - const enCoilId = this.selectedRow.ENCOILID || this.selectedRow.encoilid || null - const versionId = this.specBindSelectedId - - // 1. 先写入基础关联记录(异常数稍后更新) - await upsertProcessCoilRecord({ - versionId, - coilId, - enCoilId, - hasAnomaly: 0, - anomalyCnt: 0, - processTime: new Date().toISOString() - }) - - // 2. 异常检测(仅当存在 SEG 数据时执行) - let anomalyCnt = 0 - if (this.segData) { - try { - // 2.1 获取该版本下所有方案点位 - const planRes = await listProcessPlan({ versionId }) - const plans = planRes.rows || [] - - // 2.2 汇总所有点位的参数 - const allParams = [] - for (const plan of plans) { - const paramRes = await listProcessPlanParam({ planId: plan.planId }) - ;(paramRes.rows || []).forEach(p => allParams.push({ ...p, planId: plan.planId })) - } - - // 2.3 逐参数比对,构建异常列表 - const anomalies = [] - for (const param of allParams) { - const col = (param.paramCode || '').toUpperCase() - if (!col) continue - - // 从 SEG 系列数据取段内最大/最小值数组,再求全卷极值 - const maxArr = this.seg(col + 'MAX').filter(v => v != null) - const minArr = this.seg(col + 'MIN').filter(v => v != null) - if (!maxArr.length && !minArr.length) continue - - const actualMax = maxArr.length ? Math.max(...maxArr) : null - const actualMin = minArr.length ? Math.min(...minArr) : null - - // 从预设数据取实际设定值(如有对应映射) - let actualTarget = null - const presetCol = TREND_PRESET_MAP[col] - if (presetCol && this.presetData) { - const raw = this.presetData[presetCol] !== undefined - ? this.presetData[presetCol] - : this.presetData[presetCol.toLowerCase()] - if (raw != null) actualTarget = parseFloat(Number(raw).toFixed(4)) - } - - const upper = param.upperLimit != null ? Number(param.upperLimit) : null - const lower = param.lowerLimit != null ? Number(param.lowerLimit) : null - if (upper == null && lower == null) continue // 未配置限值,跳过 - - const overMax = upper != null && actualMax != null && actualMax > upper - const underMin = lower != null && actualMin != null && actualMin < lower - if (!overMax && !underMin) continue - - const anomalyType = (overMax && underMin) ? 'BOTH' : (overMax ? 'OVER_MAX' : 'UNDER_MIN') - - anomalies.push({ - versionId, - planId: param.planId, - paramId: param.paramId, - coilId, - enCoilId, - paramCode: param.paramCode, - paramName: param.paramName, - unit: param.unit || TREND_UNIT_MAP[col] || null, - anomalyType, - storedTarget: param.targetValue != null ? Number(param.targetValue) : null, - storedUpper: upper, - storedLower: lower, - actualTarget, - actualMax, - actualMin, - deviationMax: overMax ? parseFloat((actualMax - upper).toFixed(4)) : null, - deviationMin: underMin ? parseFloat((actualMin - lower).toFixed(4)) : null, - detectedAt: new Date().toISOString() - }) - } - - anomalyCnt = anomalies.length - - // 2.4 批量写入异常记录 - if (anomalies.length > 0) { - await batchAddProcessAnomaly(anomalies) - } - - // 2.5 若有异常则更新关联记录的计数字段 - if (anomalyCnt > 0) { - await upsertProcessCoilRecord({ - versionId, - coilId, - enCoilId, - hasAnomaly: 1, - anomalyCnt, - processTime: new Date().toISOString() - }) - } - } catch (e) { - console.warn('[规程关联] 异常检测失败,跳过', e) - } - } - - const msg = anomalyCnt > 0 - ? `关联成功,检测到 ${anomalyCnt} 个参数异常` - : '关联成功,无异常' - this.$message[anomalyCnt > 0 ? 'warning' : 'success'](msg) - this.specBindDialog = false - await this.loadCoilBinding() - } catch { - this.$message.error('关联失败,请重试') - } finally { - this.specBindLoading = false - } - }, calcLengthPerTon(row) { const len = parseFloat(row.EXIT_LENGTH || row.exit_length) @@ -1389,45 +1103,5 @@ export default { font-size: 13px; } -/* ── 规程关联区块 ── */ -.spec-bind-block { - border-top: 1px solid #ebeef5; - padding-top: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} - -.bind-status { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} - -.bind-label { - font-size: 12px; - color: #909399; - white-space: nowrap; -} - -.bind-val { - font-size: 12px; - color: #303133; - font-weight: 500; - &.muted { color: #c0c4cc; font-weight: normal; } -} - -/* ── 弹窗内工具栏 ── */ -.spec-bind-toolbar { - display: flex; - align-items: center; - gap: 12px; -} - -.spec-bind-hint { - font-size: 12px; - color: #909399; -} diff --git a/klp-ui/src/views/wms/coil/typing.vue b/klp-ui/src/views/wms/coil/typing.vue index b755781b..52fd88f9 100644 --- a/klp-ui/src/views/wms/coil/typing.vue +++ b/klp-ui/src/views/wms/coil/typing.vue @@ -31,6 +31,12 @@ +
+ + + 已匹配规程:{{ matchedSpec.specCode }}{{ matchedSpec.specName ? ' - ' + matchedSpec.specName : '' }} / {{ matchedSpec.versionCode }} + +
@@ -305,6 +311,7 @@ + + diff --git a/klp-ui/src/views/wms/processSpec/coilTypingCheck.vue b/klp-ui/src/views/wms/processSpec/coilTypingCheck.vue new file mode 100644 index 00000000..8743261d --- /dev/null +++ b/klp-ui/src/views/wms/processSpec/coilTypingCheck.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/klp-ui/src/views/wms/processSpec/planSpec.vue b/klp-ui/src/views/wms/processSpec/planSpec.vue index 83ea7772..68a35388 100644 --- a/klp-ui/src/views/wms/processSpec/planSpec.vue +++ b/klp-ui/src/views/wms/processSpec/planSpec.vue @@ -85,6 +85,88 @@ >模板导入 + +
+
+ 匹配条件 +
+ + 编辑 +
+
+
+ + + + +
+
+ { + const v = res.data || {} + this.matchCondForm = { + versionId: this.versionId, + versionCode: v.versionCode || '', + specId: v.specId || undefined, + matchEntryThickMin: v.matchEntryThickMin ?? null, + matchEntryThickMax: v.matchEntryThickMax ?? null, + matchExitThickMin: v.matchExitThickMin ?? null, + matchExitThickMax: v.matchExitThickMax ?? null, + matchEntryWidthMin: v.matchEntryWidthMin ?? null, + matchEntryWidthMax: v.matchEntryWidthMax ?? null, + matchExitWidthMin: v.matchExitWidthMin ?? null, + matchExitWidthMax: v.matchExitWidthMax ?? null, + matchSteelGrade: v.matchSteelGrade || '' + } + this._matchCondSnapshot = { ...this.matchCondForm } + this.matchCondEditing = false + }) + }, + cancelMatchCond() { + this.matchCondForm = { ...this._matchCondSnapshot } + this.matchCondEditing = false + }, + saveMatchCond() { + this.matchCondSaving = true + updateProcessSpecVersion(this.matchCondForm).then(() => { + this._matchCondSnapshot = { ...this.matchCondForm } + this.matchCondEditing = false + this.$modal.msgSuccess('匹配条件保存成功') + }).catch(e => console.error(e)).finally(() => { this.matchCondSaving = false }) + }, + fmtRange(min, max, unit) { + if (min == null && max == null) return '—' + const a = min != null ? min : '∞' + const b = max != null ? max : '∞' + return `${a} – ${b} ${unit}` } } } @@ -1347,6 +1474,78 @@ export default { .seg-process { background: #f0f9eb; color: #3a7a2a; border: 1px solid #b3e19d; } .seg-outlet { background: #fdf6ec; color: #a86a00; border: 1px solid #f5dab1; } +/* ── 匹配条件面板 ── */ +.match-cond-panel { + display: flex; + flex-direction: column; + gap: 0; + border: 1px solid #e4e9f0; + border-radius: 6px; + background: #f8fafd; + margin-bottom: 12px; + overflow: hidden; +} +.match-cond-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 7px 12px; + background: #edf2f8; + border-bottom: 1px solid #e4e9f0; +} +.match-cond-title { + font-size: 12px; + font-weight: 600; + color: #4a6585; + letter-spacing: .3px; +} +.match-cond-title i { margin-right: 5px; } +.match-cond-actions { display: flex; gap: 6px; } +.mci-val { + font-size: 12px; + color: #303133; + font-variant-numeric: tabular-nums; +} +.match-cond-body { + display: flex; + align-items: center; + flex-wrap: wrap; + padding: 8px 12px; + gap: 0; +} +.match-cond-item { + display: flex; + align-items: center; + gap: 5px; + padding: 2px 0; +} +.mci-label { + font-size: 12px; + color: #606266; + white-space: nowrap; + min-width: 60px; +} +.mci-input { + width: 82px !important; +} +.mci-sep { + color: #c0c4cc; + font-size: 13px; + flex-shrink: 0; +} +.mci-unit { + font-size: 11px; + color: #909399; + flex-shrink: 0; +} +.match-cond-divider { + width: 1px; + height: 20px; + background: #e4e9f0; + margin: 0 12px; + flex-shrink: 0; +} + /* 导入对话框样式 */ .import-container { diff --git a/klp-wms/src/main/java/com/klp/domain/WmsMaterialCoil.java b/klp-wms/src/main/java/com/klp/domain/WmsMaterialCoil.java index e1273b70..51c84335 100644 --- a/klp-wms/src/main/java/com/klp/domain/WmsMaterialCoil.java +++ b/klp-wms/src/main/java/com/klp/domain/WmsMaterialCoil.java @@ -210,5 +210,8 @@ public class WmsMaterialCoil extends BaseEntity { * 调拨类型 */ private String transferType; + + private Long specId; + private Long versionId; } diff --git a/klp-wms/src/main/java/com/klp/domain/WmsProcessSpecVersion.java b/klp-wms/src/main/java/com/klp/domain/WmsProcessSpecVersion.java index f088ec17..9d71f903 100644 --- a/klp-wms/src/main/java/com/klp/domain/WmsProcessSpecVersion.java +++ b/klp-wms/src/main/java/com/klp/domain/WmsProcessSpecVersion.java @@ -7,6 +7,8 @@ import com.klp.common.core.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; +import java.math.BigDecimal; + /** * 规程版本对象 wms_process_spec_version * @@ -46,4 +48,14 @@ public class WmsProcessSpecVersion extends BaseEntity { private Integer delFlag; private String remark; + + private BigDecimal matchEntryThickMin; + private BigDecimal matchEntryThickMax; + private BigDecimal matchExitThickMin; + private BigDecimal matchExitThickMax; + private BigDecimal matchEntryWidthMin; + private BigDecimal matchEntryWidthMax; + private BigDecimal matchExitWidthMin; + private BigDecimal matchExitWidthMax; + private String matchSteelGrade; } diff --git a/klp-wms/src/main/java/com/klp/domain/WmsProductionLine.java b/klp-wms/src/main/java/com/klp/domain/WmsProductionLine.java index e02d5ad0..f549d8c2 100644 --- a/klp-wms/src/main/java/com/klp/domain/WmsProductionLine.java +++ b/klp-wms/src/main/java/com/klp/domain/WmsProductionLine.java @@ -50,4 +50,9 @@ public class WmsProductionLine extends BaseEntity { */ private String remark; + /** + * 产线支持的操作类型(逗号分隔,1=分卷,2=合卷,3=更新) + */ + private String actionType; + } diff --git a/klp-wms/src/main/java/com/klp/domain/bo/WmsMaterialCoilBo.java b/klp-wms/src/main/java/com/klp/domain/bo/WmsMaterialCoilBo.java index bf74adc7..4f4f15d5 100644 --- a/klp-wms/src/main/java/com/klp/domain/bo/WmsMaterialCoilBo.java +++ b/klp-wms/src/main/java/com/klp/domain/bo/WmsMaterialCoilBo.java @@ -378,5 +378,8 @@ public class WmsMaterialCoilBo extends BaseEntity { */ @TableField(exist = false) private Boolean onlyEmptyPackingStatus; + + private Long specId; + private Long versionId; } diff --git a/klp-wms/src/main/java/com/klp/domain/bo/WmsProcessSpecVersionBo.java b/klp-wms/src/main/java/com/klp/domain/bo/WmsProcessSpecVersionBo.java index b016f3b6..e48e6321 100644 --- a/klp-wms/src/main/java/com/klp/domain/bo/WmsProcessSpecVersionBo.java +++ b/klp-wms/src/main/java/com/klp/domain/bo/WmsProcessSpecVersionBo.java @@ -8,6 +8,7 @@ import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.math.BigDecimal; /** * 规程版本业务对象 wms_process_spec_version @@ -38,4 +39,14 @@ public class WmsProcessSpecVersionBo extends BaseEntity { private String status; private String remark; + + private BigDecimal matchEntryThickMin; + private BigDecimal matchEntryThickMax; + private BigDecimal matchExitThickMin; + private BigDecimal matchExitThickMax; + private BigDecimal matchEntryWidthMin; + private BigDecimal matchEntryWidthMax; + private BigDecimal matchExitWidthMin; + private BigDecimal matchExitWidthMax; + private String matchSteelGrade; } diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilVo.java index 686048ff..778b965e 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsMaterialCoilVo.java @@ -334,5 +334,10 @@ public class WmsMaterialCoilVo extends BaseEntity { * 关联的订单列表(通过wms_coil_contract_rel中间表JOIN crm_order) */ private List orderList; + + private Long specId; + private Long versionId; + private String specCode; + private String versionCode; } diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsProcessSpecVersionVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsProcessSpecVersionVo.java index dd3e7224..5ef1766d 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsProcessSpecVersionVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsProcessSpecVersionVo.java @@ -5,6 +5,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; +import java.math.BigDecimal; import java.util.Date; /** @@ -41,4 +42,18 @@ public class WmsProcessSpecVersionVo { @ExcelProperty(value = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; + + private BigDecimal matchEntryThickMin; + private BigDecimal matchEntryThickMax; + private BigDecimal matchExitThickMin; + private BigDecimal matchExitThickMax; + private BigDecimal matchEntryWidthMin; + private BigDecimal matchEntryWidthMax; + private BigDecimal matchExitWidthMin; + private BigDecimal matchExitWidthMax; + private String matchSteelGrade; + + private String specCode; + private String specName; + private Long lineId; } diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsProductionLineVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsProductionLineVo.java index 5011bd54..240a7cf9 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsProductionLineVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsProductionLineVo.java @@ -64,6 +64,11 @@ public class WmsProductionLineVo { @ExcelProperty(value = "备注") private String remark; + /** + * 产线支持的操作类型(逗号分隔,1=分卷,2=合卷,3=更新) + */ + private String actionType; + /** * 关联的排产计划明细数量 */ diff --git a/klp-wms/src/main/java/com/klp/mapper/WmsCoilPendingActionMapper.java b/klp-wms/src/main/java/com/klp/mapper/WmsCoilPendingActionMapper.java index 7b7061bf..9c3af748 100644 --- a/klp-wms/src/main/java/com/klp/mapper/WmsCoilPendingActionMapper.java +++ b/klp-wms/src/main/java/com/klp/mapper/WmsCoilPendingActionMapper.java @@ -7,6 +7,7 @@ import com.klp.domain.WmsCoilPendingAction; import com.klp.domain.vo.WmsCoilPendingActionVo; import com.klp.domain.vo.WmsCoilPendingActionIdCoilVo; import org.apache.ibatis.annotations.Param; +import java.util.List; /** * 钢卷待操作Mapper接口 @@ -44,5 +45,29 @@ public interface WmsCoilPendingActionMapper extends BaseMapperPlus selectActionTypeByProcessedCoilIds(@Param("coilIds") List coilIds); + + /** + * 获取所有有效待操作记录的 processed_coil_ids 字符串列表(供规程同步主键展开用)。 + */ + List selectAllProcessedCoilIdStrings(); + + /** + * 获取所有有效待操作记录的 processed_coil_ids + action_status + action_type, + * 供规程同步页面同时构建"已处理钢卷 ID 集"和"录入状态"映射。 + */ + List selectAllProcessedCoilIdsAndActionStatus(); + + /** + * 规程同步:按 actionType 集合查询全部待操作记录(含未录入和已录入)。 + * 返回 action_id / coil_id / action_type / action_status / processed_coil_ids 五列。 + */ + List selectPendingByActionTypes( + @Param("actionTypes") java.util.Collection actionTypes); } diff --git a/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java b/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java index 0438d0c7..f893c0cf 100644 --- a/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java +++ b/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java @@ -47,6 +47,8 @@ public interface WmsMaterialCoilMapper extends BaseMapperPlus selectVoListWithDynamicJoin(@Param("ew")QueryWrapper lqw); + Map selectCountForSpecSync(@Param("ew") QueryWrapper qw); + List> getDistributionByActualWarehouse(@Param("itemType") String itemType, @Param("itemId") Long itemId); List> getDistributionByActualItemType(@Param("itemType")String itemType,@Param("itemId") Long itemId); diff --git a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java index 8da3049f..7a2c076c 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java @@ -55,6 +55,44 @@ public interface IWmsMaterialCoilService { */ List queryList(WmsMaterialCoilBo bo); + /** + * 按入场卷号批量查询(供规程同步分页列表使用,IN 查询避免 N 次单查) + */ + List queryByEnterCoilNos(List enterCoilNos); + + /** + * 规程同步专用:DB 层分页查询(避免全表加载),支持 material LIKE 过滤和 syncStatus 过滤。 + * specIds 不为空时追加 mc.spec_id IN (...) 条件。 + */ + List queryPageForSpecSync(WmsMaterialCoilBo bo, int pageNum, int pageSize, + String syncStatus, String material, java.util.Set specIds); + + /** + * 规程同步专用:一次 SQL 返回 total/synced/unsynced 三个计数。 + */ + Map countForSpecSync(WmsMaterialCoilBo bo, String material, java.util.Set specIds); + + /** + * 规程同步专用:以 processed_coil_ids 展开后的 coilId 集合为主,分页查 L3 钢卷(含 item join)。 + */ + List queryByProcessedCoilIds(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + String syncStatus, java.util.Set filterSpecIds, int offset, int pageSize); + + /** + * 规程同步专用:以 processed_coil_ids 展开后的 coilId 集合为主,计算满足 L3 过滤条件的总数。 + */ + long countByProcessedCoilIds(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + String syncStatus, java.util.Set filterSpecIds); + + /** + * 规程同步专用:不区分 syncStatus,一次查询返回 total/synced/unsynced/movedOn 整体汇总(用于统计条)。 + */ + java.util.Map getOverallSyncStats(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + java.util.Set filterSpecIds); + /** * 统计筛选条件下的全量汇总数据(高性能:只查sum/count) * 独立的统计接口,不影响分页查询 diff --git a/klp-wms/src/main/java/com/klp/service/IWmsProcessSpecVersionService.java b/klp-wms/src/main/java/com/klp/service/IWmsProcessSpecVersionService.java index b19ac48d..588d29d3 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsProcessSpecVersionService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsProcessSpecVersionService.java @@ -5,6 +5,7 @@ import com.klp.common.core.page.TableDataInfo; import com.klp.domain.bo.WmsProcessSpecVersionBo; import com.klp.domain.vo.WmsProcessSpecVersionVo; +import java.math.BigDecimal; import java.util.Collection; import java.util.List; @@ -31,4 +32,25 @@ public interface IWmsProcessSpecVersionService { * 将指定版本设为当前规程下唯一生效版本 */ Boolean activateVersion(Long versionId); + + /** + * 根据L2实绩参数匹配最佳规程版本。 + * 对所有 isActive=1 的版本评分(命中条件最多者胜出),返回匹配的版本VO(含specCode/specName),无匹配返回null。 + * lineId 不为空时,只在属于该产线的规程版本中匹配。 + */ + WmsProcessSpecVersionVo matchBestVersion(BigDecimal entryThick, BigDecimal exitThick, + BigDecimal entryWidth, BigDecimal exitWidth, + String grade, Long lineId); + + /** + * 加载所有生效版本并批量填充 specCode/specName,供分页列表一次性预加载使用。 + * lineId 不为空时只返回属于该产线的版本。 + */ + List queryActiveVersionsEnriched(Long lineId); + + /** + * 加载全部版本(含非生效版本)并批量填充 specCode/specName/lineId。 + * 用于展示已绑定规程信息,避免钢卷绑定旧版本后查不到名称。 + */ + List queryAllVersionsEnriched(); } diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java index 73fd96b1..c6114e3e 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java @@ -1181,6 +1181,122 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { return list; } + @Override + public List queryByEnterCoilNos(List enterCoilNos) { + if (enterCoilNos == null || enterCoilNos.isEmpty()) { + return Collections.emptyList(); + } + QueryWrapper qw = new QueryWrapper<>(); + qw.in("mc.enter_coil_no", enterCoilNos).eq("mc.del_flag", 0); + return baseMapper.selectVoListWithDynamicJoin(qw); + } + + @Override + public List queryPageForSpecSync(WmsMaterialCoilBo bo, int pageNum, int pageSize, + String syncStatus, String material, java.util.Set specIds) { + QueryWrapper qw = buildQueryWrapperPlus(bo); + applySpecSyncExtras(qw, syncStatus, material, specIds); + int offset = Math.max(0, (pageNum - 1) * pageSize); + qw.last("LIMIT " + offset + ", " + pageSize); + return baseMapper.selectVoListWithDynamicJoin(qw); + } + + @Override + public Map countForSpecSync(WmsMaterialCoilBo bo, String material, java.util.Set specIds) { + QueryWrapper qw = buildQueryWrapperPlus(bo); + applySpecSyncExtras(qw, null, material, specIds); + return baseMapper.selectCountForSpecSync(qw); + } + + private void applySpecSyncExtras(QueryWrapper qw, String syncStatus, + String material, java.util.Set specIds) { + if (StringUtils.isNotBlank(material)) { + String like = "%" + material.toLowerCase() + "%"; + qw.apply("LOWER(CASE WHEN mc.item_type = 'raw_material' THEN COALESCE(rm.material,'') " + + "ELSE COALESCE(p.material,'') END) LIKE {0}", like); + } + if (specIds != null && !specIds.isEmpty()) { + qw.in("mc.spec_id", specIds); + } + if ("synced".equals(syncStatus)) qw.isNotNull("mc.spec_id"); + else if ("unsynced".equals(syncStatus)) qw.isNull("mc.spec_id"); + } + + @Override + public List queryByProcessedCoilIds(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + String syncStatus, java.util.Set filterSpecIds, int offset, int pageSize) { + if (coilIds == null || coilIds.isEmpty()) return Collections.emptyList(); + QueryWrapper qw = buildProcessedCoilQw(coilIds, enterCoilNo, currentCoilNo, + material, qualityStatus, syncStatus, filterSpecIds); + qw.last("LIMIT " + offset + ", " + pageSize); + return baseMapper.selectVoListWithDynamicJoin(qw); + } + + @Override + public long countByProcessedCoilIds(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + String syncStatus, java.util.Set filterSpecIds) { + if (coilIds == null || coilIds.isEmpty()) return 0L; + QueryWrapper qw = buildProcessedCoilQw(coilIds, enterCoilNo, currentCoilNo, + material, qualityStatus, syncStatus, filterSpecIds); + Map result = baseMapper.selectCountForSpecSync(qw); + if (result == null) return 0L; + Object total = result.get("total"); + if (total == null) return 0L; + try { return Long.parseLong(total.toString()); } catch (NumberFormatException e) { return 0L; } + } + + @Override + public java.util.Map getOverallSyncStats(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + java.util.Set filterSpecIds) { + java.util.Map stats = new java.util.LinkedHashMap<>(); + stats.put("total", 0L); + stats.put("synced", 0L); + stats.put("unsynced", 0L); + stats.put("movedOn", 0L); + if (coilIds == null || coilIds.isEmpty()) return stats; + // syncStatus=null → 不加 version_id 过滤,一次查出全量分组 + QueryWrapper qw = buildProcessedCoilQw( + coilIds, enterCoilNo, currentCoilNo, material, qualityStatus, null, filterSpecIds); + Map raw = baseMapper.selectCountForSpecSync(qw); + if (raw == null) return stats; + stats.put("total", safeLong(raw, "total")); + stats.put("synced", safeLong(raw, "synced")); + stats.put("unsynced", safeLong(raw, "unsynced")); + stats.put("movedOn", safeLong(raw, "movedOn")); + return stats; + } + + private long safeLong(Map map, String key) { + Object v = map.get(key); + if (v == null) return 0L; + try { return Long.parseLong(v.toString()); } catch (NumberFormatException e) { return 0L; } + } + + private QueryWrapper buildProcessedCoilQw(java.util.Collection coilIds, + String enterCoilNo, String currentCoilNo, String material, String qualityStatus, + String syncStatus, java.util.Set filterSpecIds) { + QueryWrapper qw = new QueryWrapper<>(); + // 不过滤 data_type:data_type=0(已流转/历史)和 data_type=1(当前)均展示 + // movedOn 状态仅作页面标记,不阻断规程绑定 + qw.in("mc.coil_id", coilIds).eq("mc.del_flag", 0); + if (StringUtils.isNotBlank(enterCoilNo)) qw.like("mc.enter_coil_no", enterCoilNo); + if (StringUtils.isNotBlank(currentCoilNo)) qw.like("mc.current_coil_no", currentCoilNo); + if (StringUtils.isNotBlank(qualityStatus)) qw.eq("mc.quality_status", qualityStatus); + if (StringUtils.isNotBlank(material)) { + qw.apply("LOWER(CASE WHEN mc.item_type = 'raw_material' THEN COALESCE(rm.material,'') " + + "ELSE COALESCE(p.material,'') END) LIKE {0}", "%" + material.toLowerCase() + "%"); + } + if (filterSpecIds != null && !filterSpecIds.isEmpty()) qw.in("mc.spec_id", filterSpecIds); + // 用 version_id 判断是否已绑规程,与控制层 coil.getVersionId()!=null 的逻辑保持一致 + // spec_id 有时可能为空(老数据只写了 version_id),用 version_id 更可靠 + if ("synced".equals(syncStatus)) qw.isNotNull("mc.version_id"); + if ("unsynced".equals(syncStatus)) qw.isNull("mc.version_id"); + return qw; + } + @Override public String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo) { if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) { diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsProcessSpecVersionServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsProcessSpecVersionServiceImpl.java index dfcfafc5..9d0f8544 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsProcessSpecVersionServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsProcessSpecVersionServiceImpl.java @@ -24,8 +24,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * 规程版本Service实现 @@ -139,6 +143,135 @@ public class WmsProcessSpecVersionServiceImpl implements IWmsProcessSpecVersionS return baseMapper.updateById(one) > 0; } + @Override + public WmsProcessSpecVersionVo matchBestVersion(BigDecimal entryThick, BigDecimal exitThick, + BigDecimal entryWidth, BigDecimal exitWidth, + String grade, Long lineId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(WmsProcessSpecVersion::getIsActive, 1); + List versions = baseMapper.selectVoList(lqw); + if (versions.isEmpty()) { + return null; + } + + // 按产线ID过滤:只匹配属于指定产线的规程版本 + if (lineId != null) { + List specIds = versions.stream() + .map(WmsProcessSpecVersionVo::getSpecId).distinct().collect(Collectors.toList()); + LambdaQueryWrapper slqw = Wrappers.lambdaQuery(); + slqw.in(WmsProcessSpec::getSpecId, specIds); + slqw.eq(WmsProcessSpec::getLineId, lineId); + Set validSpecIds = wmsProcessSpecMapper.selectList(slqw) + .stream().map(WmsProcessSpec::getSpecId).collect(Collectors.toSet()); + versions = versions.stream() + .filter(v -> validSpecIds.contains(v.getSpecId())).collect(Collectors.toList()); + if (versions.isEmpty()) { + return null; + } + } + + WmsProcessSpecVersionVo best = null; + int bestScore = -1; + + for (WmsProcessSpecVersionVo v : versions) { + int score = 0; + if (entryThick != null && v.getMatchEntryThickMin() != null && v.getMatchEntryThickMax() != null + && entryThick.compareTo(v.getMatchEntryThickMin()) >= 0 + && entryThick.compareTo(v.getMatchEntryThickMax()) <= 0) { + score++; + } + if (exitThick != null && v.getMatchExitThickMin() != null && v.getMatchExitThickMax() != null + && exitThick.compareTo(v.getMatchExitThickMin()) >= 0 + && exitThick.compareTo(v.getMatchExitThickMax()) <= 0) { + score++; + } + if (entryWidth != null && v.getMatchEntryWidthMin() != null && v.getMatchEntryWidthMax() != null + && entryWidth.compareTo(v.getMatchEntryWidthMin()) >= 0 + && entryWidth.compareTo(v.getMatchEntryWidthMax()) <= 0) { + score++; + } + if (exitWidth != null && v.getMatchExitWidthMin() != null && v.getMatchExitWidthMax() != null + && exitWidth.compareTo(v.getMatchExitWidthMin()) >= 0 + && exitWidth.compareTo(v.getMatchExitWidthMax()) <= 0) { + score++; + } + if (StringUtils.isNotBlank(grade) && StringUtils.isNotBlank(v.getMatchSteelGrade()) + && grade.toLowerCase().contains(v.getMatchSteelGrade().toLowerCase())) { + score++; + } + if (score > bestScore) { + bestScore = score; + best = v; + } + } + + if (best == null || bestScore == 0) { + return null; + } + + WmsProcessSpec spec = wmsProcessSpecMapper.selectById(best.getSpecId()); + if (spec != null) { + best.setSpecCode(spec.getSpecCode()); + best.setSpecName(spec.getSpecName()); + } + return best; + } + + @Override + public List queryActiveVersionsEnriched(Long lineId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(WmsProcessSpecVersion::getIsActive, 1); + List versions = baseMapper.selectVoList(lqw); + if (versions.isEmpty()) { + return versions; + } + + if (lineId != null) { + List specIds = versions.stream() + .map(WmsProcessSpecVersionVo::getSpecId).distinct().collect(Collectors.toList()); + LambdaQueryWrapper slqw = Wrappers.lambdaQuery(); + slqw.in(WmsProcessSpec::getSpecId, specIds); + slqw.eq(WmsProcessSpec::getLineId, lineId); + Set validSpecIds = wmsProcessSpecMapper.selectList(slqw) + .stream().map(WmsProcessSpec::getSpecId).collect(Collectors.toSet()); + versions = versions.stream() + .filter(v -> validSpecIds.contains(v.getSpecId())).collect(Collectors.toList()); + if (versions.isEmpty()) { + return versions; + } + } + + enrichVersionsWithSpec(versions); + return versions; + } + + @Override + public List queryAllVersionsEnriched() { + // 不过滤 isActive,获取全部版本(含历史版本),用于展示已绑定规程名称 + List versions = baseMapper.selectVoList(Wrappers.lambdaQuery()); + if (versions.isEmpty()) return versions; + enrichVersionsWithSpec(versions); + return versions; + } + + /** 批量填充 specCode / specName / lineId(公共逻辑) */ + private void enrichVersionsWithSpec(List versions) { + List specIds = versions.stream() + .map(WmsProcessSpecVersionVo::getSpecId).distinct().collect(Collectors.toList()); + LambdaQueryWrapper sq = Wrappers.lambdaQuery(); + sq.in(WmsProcessSpec::getSpecId, specIds); + Map specMap = wmsProcessSpecMapper.selectList(sq).stream() + .collect(Collectors.toMap(WmsProcessSpec::getSpecId, s -> s, (a, b) -> a)); + for (WmsProcessSpecVersionVo v : versions) { + WmsProcessSpec spec = specMap.get(v.getSpecId()); + if (spec != null) { + v.setSpecCode(spec.getSpecCode()); + v.setSpecName(spec.getSpecName()); + v.setLineId(spec.getLineId()); + } + } + } + @Override @Transactional(rollbackFor = Exception.class) public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { diff --git a/klp-wms/src/main/resources/mapper/klp/WmsCoilPendingActionMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsCoilPendingActionMapper.xml index 54a3da3d..8d62ec34 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsCoilPendingActionMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsCoilPendingActionMapper.xml @@ -182,7 +182,47 @@ WHERE action_id = #{actionId} + + + + + + + + + + diff --git a/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml index c3d1f987..865b54e3 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml @@ -40,6 +40,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -1097,5 +1099,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" LIMIT 1 + + + diff --git a/klp-wms/src/main/resources/mapper/klp/WmsProcessSpecVersionMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsProcessSpecVersionMapper.xml index 72b8a5b2..15ffb4b2 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsProcessSpecVersionMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsProcessSpecVersionMapper.xml @@ -14,6 +14,15 @@ + + + + + + + + +