Browse Source

接口开发10.15

ht 3 weeks ago
parent
commit
8c1aa5be83

+ 63 - 0
src/main/java/com/malk/minglei/config/DictionaryProperties.java

@@ -0,0 +1,63 @@
+package com.malk.minglei.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 数据字典 Oracle 数据源配置。
+ */
+@ConfigurationProperties(prefix = "minglei.dictionary")
+public class DictionaryProperties {
+
+    private DataSourceProperties datasource = new DataSourceProperties();
+
+    public DataSourceProperties getDatasource() {
+        return datasource;
+    }
+
+    public void setDatasource(DataSourceProperties datasource) {
+        this.datasource = datasource;
+    }
+
+    /**
+     * 数据字典独立数据源连接参数。
+     */
+    public static class DataSourceProperties {
+
+        private String url;
+        private String username;
+        private String password;
+        private String driverClassName = "oracle.jdbc.OracleDriver";
+
+        public String getUrl() {
+            return url;
+        }
+
+        public void setUrl(String url) {
+            this.url = url;
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+        public void setUsername(String username) {
+            this.username = username;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public void setPassword(String password) {
+            this.password = password;
+        }
+
+        public String getDriverClassName() {
+            return driverClassName;
+        }
+
+        public void setDriverClassName(String driverClassName) {
+            this.driverClassName = driverClassName;
+        }
+    }
+}

+ 18 - 0
src/main/java/com/malk/minglei/service/dictionary/DictionaryQueryService.java

@@ -0,0 +1,18 @@
+package com.malk.minglei.service.dictionary;
+
+import java.util.Map;
+
+/**
+ * 对外数据字典查询服务。
+ */
+public interface DictionaryQueryService {
+
+    /**
+     * 根据字典编码将请求分派给对应的服务端查询定义。
+     *
+     * @param dictionaryCode 数据字典编码
+     * @param filters 查询筛选条件
+     * @return 包含分页信息和字典记录的查询结果
+     */
+    Map<String, Object> query(String dictionaryCode, Map<String, String> filters);
+}

+ 579 - 0
src/main/java/com/malk/minglei/service/impl/dictionary/DictionaryQueryServiceImpl.java

@@ -0,0 +1,579 @@
+package com.malk.minglei.service.impl.dictionary;
+
+import com.malk.minglei.service.dictionary.DictionaryQueryService;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 集中维护数据字典编码、固定 SQL 与筛选参数规则的数据字典服务实现。
+ */
+@Service
+public class DictionaryQueryServiceImpl implements DictionaryQueryService {
+
+    // 供应商视图需要据点和公司共同限定,可按供应商名称模糊筛选。
+    private static final String ERP_SUPPLIER_DICTIONARY_CODE = "erp-supplier";
+    private static final String ERP_SUPPLIER_DATA_SQL = "select * from DSDATA.erp_apmt440_pmaauc_v " +
+            "where pmabsite = :t100id and pmaaent = :ent";
+    private static final String ERP_SUPPLIER_COUNT_SQL = "select count(1) from DSDATA.erp_apmt440_pmaauc_v " +
+            "where pmabsite = :t100id and pmaaent = :ent";
+
+    // 明磊包材B9开窗视图仅保留指定编码规则和中文品名,并支持统一关键字匹配。
+    private static final String ERP_PACKAGING_B9_WINDOW_VIEW_DICTIONARY_CODE = "erp-packaging-b9-window-view";
+    // 兼容已接入的旧接口路径,新的调用方应使用 ERP_PACKAGING_B9_WINDOW_VIEW_DICTIONARY_CODE。
+    private static final String LEGACY_ERP_MATERIAL_DICTIONARY_CODE = "erp-material";
+    private static final String ERP_PACKAGING_B9_WINDOW_VIEW_DATA_SQL = "select imaf001, imaal003, imaal004 " +
+            "from DSDATA.imaf_t " +
+            "inner join DSDATA.imaal_t on imafent = imaalent and imaf001 = imaal001 and imaal002 = 'zh_CN' " +
+            "where imafent = :ent and imafsite = :t100id " +
+            "and imaf016 <> 'TY' and imaf016 <> 'LTY' " +
+            "and (substr(imaf001,1,2) = 'B9' or substr(imaf001,1,3) = 'VB9' or substr(imaf001,1,1) = 'J')";
+    private static final String ERP_PACKAGING_B9_WINDOW_VIEW_COUNT_SQL = "select count(1) " +
+            "from DSDATA.imaf_t " +
+            "inner join DSDATA.imaal_t on imafent = imaalent and imaf001 = imaal001 and imaal002 = 'zh_CN' " +
+            "where imafent = :ent and imafsite = :t100id " +
+            "and imaf016 <> 'TY' and imaf016 <> 'LTY' " +
+            "and (substr(imaf001,1,2) = 'B9' or substr(imaf001,1,3) = 'VB9' or substr(imaf001,1,1) = 'J')";
+
+    // 包材材质将 ERP 编码转换为中文类型,使查询与展示使用一致的文本。
+    private static final String ERP_PACKAGING_MATERIAL_DICTIONARY_CODE = "erp-packaging-material";
+    private static final String ERP_PACKAGING_MATERIAL_TYPE_SQL = "case pmaauc016 when '1' then '说明书' " +
+            "when '2' then '彩盒' when '3' then '纸箱' when '4' then '标贴' end";
+    private static final String ERP_PACKAGING_MATERIAL_DATA_SQL = "select pmaauc001, pmaauc002, " +
+            ERP_PACKAGING_MATERIAL_TYPE_SQL + " tp " +
+            "from DSDATA.pmaauc_t where pmaaucent = :ent and pmaauc016 is not null";
+    private static final String ERP_PACKAGING_MATERIAL_COUNT_SQL = "select count(1) from DSDATA.pmaauc_t " +
+            "where pmaaucent = :ent and pmaauc016 is not null";
+
+    // 料件视图直接返回视图完整字段,可按中文品名模糊筛选。
+    private static final String ERP_ITEM_VIEW_DICTIONARY_CODE = "erp-item-view";
+    private static final String ERP_ITEM_VIEW_DATA_SQL = "select * from DSDATA.erp_apmt440_imaauc_v " +
+            "where imaalent = :ent";
+    private static final String ERP_ITEM_VIEW_COUNT_SQL = "select count(1) from DSDATA.erp_apmt440_imaauc_v " +
+            "where imaalent = :ent";
+
+    // 销售订单开窗固定查询明磊公司已审核且仍有未交数量的非指定客户订单,并补充客户、业务员和料件中文信息。
+    private static final String ERP_SALES_ORDER_DICTIONARY_CODE = "erp-sales-order";
+    private static final String ERP_SALES_ORDER_DATA_SQL =
+            "select distinct xmdasite, xmdadocno, xmdcud010, to_char(xmdadocdt, 'yyyy-mm-dd') xmdadocdt, " +
+                    "(select ooag011 from DSDATA.ooag_t where ooagent = xmdaent and ooag001 = xmda002) xmda002_desc, " +
+                    "xmda003, " +
+                    "(select ooefl003 from DSDATA.ooefl_t where ooeflent = xmdaent and ooefl001 = xmda003 " +
+                    "and ooefl002 = 'zh_CN') xmda003_desc, " +
+                    "(select pmaal003 from DSDATA.pmaal_t where pmaalent = xmdaent and pmaal001 = xmda004 " +
+                    "and pmaal002 = 'zh_CN') xmda004_desc, " +
+                    "xmdc001, " +
+                    "(select imaal003 from DSDATA.imaal_t where imaalent = xmdcent and imaal001 = xmdc001 " +
+                    "and imaal002 = 'zh_CN') xmdc001_desc, " +
+                    "(select imaal004 from DSDATA.imaal_t where imaalent = xmdcent and imaal001 = xmdc001 " +
+                    "and imaal002 = 'zh_CN') xmdc001_spec, " +
+                    "xmdc002, xmdc007, xmdd006, xmdd014, xmdd015, ooff013 " +
+                    "from DSDATA.xmda_t " +
+                    "left join DSDATA.xmdc_t on xmdaent = xmdcent and xmdasite = xmdcsite and xmdadocno = xmdcdocno " +
+                    "left join DSDATA.xmdd_t on xmdcent = xmddent and xmdcsite = xmddsite and xmdcdocno = xmdddocno " +
+                    "and xmdcseq = xmddseq " +
+                    "left join DSDATA.ooff_t on xmdcent = ooffent and xmdcdocno = ooff003 and xmdcseq = ooff004 " +
+                    "and ooff002 = 'axmt500' " +
+                    "where xmdaent = 86 and xmdasite = :t100id " +
+                    "and xmda003 <> 'L0501' and xmda003 <> 'G0899' and xmda003 <> 'MLJT0025' and xmda003 <> 'MLJT0006' " +
+                    "and xmdd006 > (xmdd014 - xmdd015) and substr(xmdc001, 1, 1) <> '6' " +
+                    "and xmda003 <> 'ML03030000' and xmda003 <> 'ML03020200' and xmdastus = 'Y'";
+    private static final String ERP_SALES_ORDER_COUNT_SQL = "select count(1) from (" + ERP_SALES_ORDER_DATA_SQL + ")";
+
+    // 生产令开窗沿用销售订单主明细信息,但按拥有者权限筛选,且不限制未交数量。
+    private static final String ERP_PRODUCTION_ORDER_DICTIONARY_CODE = "erp-production-order";
+    private static final String ERP_PRODUCTION_ORDER_DATA_SQL =
+            "select distinct xmdasite, xmdadocno, to_char(xmdadocdt, 'yyyy-mm-dd') xmdadocdt, xmdcud010, " +
+                    "(select ooag011 from DSDATA.ooag_t where ooagent = xmdaent and ooag001 = xmda002) xmda002_desc, " +
+                    "xmda003, " +
+                    "(select ooefl003 from DSDATA.ooefl_t where ooeflent = xmdaent and ooefl001 = xmda003 " +
+                    "and ooefl002 = 'zh_CN') xmda003_desc, " +
+                    "(select pmaal003 from DSDATA.pmaal_t where pmaalent = xmdaent and pmaal001 = xmda004 " +
+                    "and pmaal002 = 'zh_CN') xmda004_desc, " +
+                    "xmdc001, " +
+                    "(select imaal003 from DSDATA.imaal_t where imaalent = xmdcent and imaal001 = xmdc001 " +
+                    "and imaal002 = 'zh_CN') xmdc001_desc, " +
+                    "(select imaal004 from DSDATA.imaal_t where imaalent = xmdcent and imaal001 = xmdc001 " +
+                    "and imaal002 = 'zh_CN') xmdc001_spec, " +
+                    "xmdc002, xmdc007, xmdd006, xmdd014, xmdd015, ooff013 " +
+                    "from DSDATA.xmda_t " +
+                    "left join DSDATA.xmdc_t on xmdaent = xmdcent and xmdasite = xmdcsite and xmdadocno = xmdcdocno " +
+                    "left join DSDATA.xmdd_t on xmdcent = xmddent and xmdcsite = xmddsite and xmdcdocno = xmdddocno " +
+                    "and xmdcseq = xmddseq " +
+                    "left join DSDATA.ooff_t on xmdcent = ooffent and xmdcdocno = ooff003 and xmdcseq = ooff004 " +
+                    "and ooff002 = 'axmt500' " +
+                    "where xmdaent = 86 and xmdasite = :t100id " +
+                    "and xmda003 <> 'L0501' and xmda003 <> 'G0899' and xmda003 <> 'MLJT0025' and xmda003 <> 'MLJT0006' " +
+                    "and substr(xmdc001, 1, 1) <> '6' and xmda003 <> 'ML03030000' " +
+                    "and xmda003 <> 'ML03020200' and xmdastus = 'Y' and xmdaownid in (:qx)";
+    private static final String ERP_PRODUCTION_ORDER_COUNT_SQL = "select count(1) from (" + ERP_PRODUCTION_ORDER_DATA_SQL + ")";
+
+    // 委外采购单开窗固定查询明磊公司与 ML 据点,采购单号来自宜搭表单字段。
+    private static final String ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_DICTIONARY_CODE =
+            "erp-outsourced-purchase-order-window";
+    private static final String OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER = "formtable_main_158_dt1_wwcgdh";
+    private static final String ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_DATA_SQL =
+            "select pmdpseq, pmdp003, pmdp001, imaal003, imaal004, pmdp023 " +
+                    "from DSDATA.pmdp_t " +
+                    "left join DSDATA.imaal_t on pmdpent = imaalent and pmdp001 = imaal001 and imaal002 = 'zh_CN' " +
+                    "where pmdpent = 86 and pmdpsite = 'ML' and pmdpdocno = :" + OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER;
+    private static final String ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_COUNT_SQL =
+            "select count(1) from DSDATA.pmdp_t " +
+                    "left join DSDATA.imaal_t on pmdpent = imaalent and pmdp001 = imaal001 and imaal002 = 'zh_CN' " +
+                    "where pmdpent = 86 and pmdpsite = 'ML' and pmdpdocno = :" + OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER;
+
+    // 委外采购对应工单备料使用采购单号与项次定位采购明细,再关联工单备料与中文品名。
+    private static final String ERP_OUTSOURCED_BOM_DICTIONARY_CODE = "erp-outsourced-bom";
+    private static final String OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER = "formtable_main_158_dt1_cgxc";
+    private static final String ERP_OUTSOURCED_BOM_DATA_SQL =
+            "select sfba006, imaal003, imaal004, sfba013 " +
+                    "from DSDATA.pmdp_t " +
+                    "left join DSDATA.sfba_t on pmdpent = sfbaent and pmdpsite = sfbasite " +
+                    "and pmdp003 = sfbadocno and sfba008 = '1' " +
+                    "left join DSDATA.imaal_t on sfbaent = imaalent and sfba006 = imaal001 and imaal002 = 'zh_CN' " +
+                    "where pmdpent = 86 and pmdpsite = 'ML' and pmdpdocno = :" +
+                    OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER + " and pmdpseq = :" +
+                    OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER;
+    private static final String ERP_OUTSOURCED_BOM_COUNT_SQL =
+            "select count(1) from DSDATA.pmdp_t " +
+                    "left join DSDATA.sfba_t on pmdpent = sfbaent and pmdpsite = sfbasite " +
+                    "and pmdp003 = sfbadocno and sfba008 = '1' " +
+                    "left join DSDATA.imaal_t on sfbaent = imaalent and sfba006 = imaal001 and imaal002 = 'zh_CN' " +
+                    "where pmdpent = 86 and pmdpsite = 'ML' and pmdpdocno = :" +
+                    OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER + " and pmdpseq = :" +
+                    OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER;
+
+    // 外部接口使用公司名称,服务端统一转换为 ERP 公司编号,避免调用方依赖内部编码。
+    private static final String MINGLEI_COMPANY_NAME = "明磊";
+    private static final String VIETNAM_COMPANY_NAME = "越南";
+    private static final long DOMESTIC_COMPANY_ENT = 86L;
+    private static final long VIETNAM_COMPANY_ENT = 60L;
+    private static final int DEFAULT_PAGE = 1;
+    private static final int DEFAULT_PAGE_SIZE = 100;
+    private static final String PAGE_ROW_NUMBER_COLUMN = "DICTIONARY_PAGE_ROW_NUM";
+
+    private final ObjectProvider<NamedParameterJdbcTemplate> jdbcTemplateProvider;
+
+    /**
+     * 创建数据字典服务实现。
+     *
+     * @param jdbcTemplateProvider 数据字典 Oracle JDBC 模板提供器
+     */
+    public DictionaryQueryServiceImpl(
+            @Qualifier("dictionaryNamedParameterJdbcTemplate") ObjectProvider<NamedParameterJdbcTemplate> jdbcTemplateProvider) {
+        this.jdbcTemplateProvider = jdbcTemplateProvider;
+    }
+
+    /**
+     * 根据字典编码执行对应的固定 SQL 查询。
+     *
+     * @param dictionaryCode 数据字典编码
+     * @param filters 查询筛选条件
+     * @return 包含分页信息和字典记录的查询结果
+     */
+    @Override
+    public Map<String, Object> query(String dictionaryCode, Map<String, String> filters) {
+        // 防止服务层被非 HTTP 调用时传入 null,后续校验始终处理非空集合。
+        Map<String, String> safeFilters = filters == null ? Collections.<String, String>emptyMap() : filters;
+        if (ERP_SUPPLIER_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpSupplier(safeFilters);
+        }
+        if (ERP_PACKAGING_B9_WINDOW_VIEW_DICTIONARY_CODE.equals(dictionaryCode)
+                || LEGACY_ERP_MATERIAL_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpPackagingB9WindowView(safeFilters);
+        }
+        if (ERP_PACKAGING_MATERIAL_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpPackagingMaterial(safeFilters);
+        }
+        if (ERP_ITEM_VIEW_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpItemView(safeFilters);
+        }
+        if (ERP_SALES_ORDER_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpSalesOrder(safeFilters);
+        }
+        if (ERP_PRODUCTION_ORDER_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpProductionOrder(safeFilters);
+        }
+        if (ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpOutsourcedPurchaseOrderWindow(safeFilters);
+        }
+        if (ERP_OUTSOURCED_BOM_DICTIONARY_CODE.equals(dictionaryCode)) {
+            return queryErpOutsourcedBom(safeFilters);
+        }
+        throw new IllegalArgumentException("dictionaryCode is not registered: " + dictionaryCode);
+    }
+
+    /**
+     * 查询 ERP 供应商视图,并仅接受视图 SQL 中声明的两个筛选条件。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return ERP 供应商视图分页记录
+     */
+    private Map<String, Object> queryErpSupplier(Map<String, String> filters) {
+        validateFilters(filters, ERP_SUPPLIER_DICTIONARY_CODE, "pmaal004");
+        MapSqlParameterSource parameters = createBaseParameters(filters);
+        String dataSql = ERP_SUPPLIER_DATA_SQL;
+        String countSql = ERP_SUPPLIER_COUNT_SQL;
+        String supplierName = optionalTextFilter(filters, "pmaal004");
+        if (supplierName != null) {
+            dataSql += " and pmaal004 like :pmaal004";
+            countSql += " and pmaal004 like :pmaal004";
+            parameters.addValue("pmaal004", createLikeValue(supplierName));
+        }
+        return queryPage(dataSql, countSql, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询明磊包材 B9 开窗视图,并返回料件编号、中文品名和规格。
+     * 单一关键字会在物料编号、中文品名和规格三个字段中进行模糊匹配。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return 明磊包材 B9 开窗视图分页记录
+     */
+    private Map<String, Object> queryErpPackagingB9WindowView(Map<String, String> filters) {
+        validateFilters(filters, ERP_PACKAGING_B9_WINDOW_VIEW_DICTIONARY_CODE, "keyword");
+        MapSqlParameterSource parameters = createBaseParameters(filters);
+        String dataSql = ERP_PACKAGING_B9_WINDOW_VIEW_DATA_SQL;
+        String countSql = ERP_PACKAGING_B9_WINDOW_VIEW_COUNT_SQL;
+        String keyword = optionalTextFilter(filters, "keyword");
+        if (keyword != null) {
+            // 三个字段任一命中即可返回,避免调用方需要预先判断关键字属于哪一类字段。
+            String keywordCondition = " and (imaf001 like :keyword or imaal003 like :keyword or imaal004 like :keyword)";
+            dataSql += keywordCondition;
+            countSql += keywordCondition;
+            parameters.addValue("keyword", createLikeValue(keyword));
+        }
+        return queryPage(dataSql, countSql, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询 ERP 包材材质,材质编码转换为可直接展示的中文名称。
+     * 单一关键字会在包材编号、名称和中文材质类型三个字段中进行模糊匹配。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return ERP 包材材质分页记录
+     */
+    private Map<String, Object> queryErpPackagingMaterial(Map<String, String> filters) {
+        validateFilters(filters, ERP_PACKAGING_MATERIAL_DICTIONARY_CODE, "keyword");
+        MapSqlParameterSource parameters = createCompanyParameters(filters);
+        String dataSql = ERP_PACKAGING_MATERIAL_DATA_SQL;
+        String countSql = ERP_PACKAGING_MATERIAL_COUNT_SQL;
+        String keyword = optionalTextFilter(filters, "keyword");
+        if (keyword != null) {
+            // SQL 不能直接在 where 子句引用 tp 别名,因此复用相同 CASE 表达式匹配中文材质类型。
+            String keywordCondition = " and (pmaauc001 like :keyword or pmaauc002 like :keyword or " +
+                    ERP_PACKAGING_MATERIAL_TYPE_SQL + " like :keyword)";
+            dataSql += keywordCondition;
+            countSql += keywordCondition;
+            parameters.addValue("keyword", createLikeValue(keyword));
+        }
+        return queryPage(dataSql, countSql, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询 ERP 料件视图的全部字段,并可按中文品名模糊筛选。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return ERP 料件视图分页记录
+     */
+    private Map<String, Object> queryErpItemView(Map<String, String> filters) {
+        validateFilters(filters, ERP_ITEM_VIEW_DICTIONARY_CODE, "keyword");
+        MapSqlParameterSource parameters = createCompanyParameters(filters);
+        String dataSql = ERP_ITEM_VIEW_DATA_SQL;
+        String countSql = ERP_ITEM_VIEW_COUNT_SQL;
+        String keyword = optionalTextFilter(filters, "keyword");
+        if (keyword != null) {
+            dataSql += " and imaal003 like :keyword";
+            countSql += " and imaal003 like :keyword";
+            parameters.addValue("keyword", createLikeValue(keyword));
+        }
+        return queryPage(dataSql, countSql, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询指定据点的销售订单开窗数据,仅返回已审核且存在未交数量的订单明细。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return 销售订单开窗分页记录
+     */
+    private Map<String, Object> queryErpSalesOrder(Map<String, String> filters) {
+        validateFilters(filters, ERP_SALES_ORDER_DICTIONARY_CODE);
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue("t100id", requireTextFilter(filters, "t100id"));
+        return queryPage(ERP_SALES_ORDER_DATA_SQL, ERP_SALES_ORDER_COUNT_SQL, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询指定据点及拥有者权限范围内的生产令开窗数据。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return 生产令开窗分页记录
+     */
+    private Map<String, Object> queryErpProductionOrder(Map<String, String> filters) {
+        validateFilters(filters, ERP_PRODUCTION_ORDER_DICTIONARY_CODE, "qx");
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue("t100id", requireTextFilter(filters, "t100id"));
+        parameters.addValue("qx", requireCommaSeparatedFilter(filters, "qx"));
+        return queryPage(ERP_PRODUCTION_ORDER_DATA_SQL, ERP_PRODUCTION_ORDER_COUNT_SQL, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询指定委外采购单的明细料件信息。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return 委外采购单开窗分页记录
+     */
+    private Map<String, Object> queryErpOutsourcedPurchaseOrderWindow(Map<String, String> filters) {
+        validateFilters(filters, ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_DICTIONARY_CODE,
+                OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER);
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue(OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER,
+                requireTextFilter(filters, OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER));
+        return queryPage(ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_DATA_SQL,
+                ERP_OUTSOURCED_PURCHASE_ORDER_WINDOW_COUNT_SQL, parameters, createPageRequest());
+    }
+
+    /**
+     * 查询指定委外采购单项次对应工单的备料明细。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @return 委外采购对应工单备料分页记录
+     */
+    private Map<String, Object> queryErpOutsourcedBom(Map<String, String> filters) {
+        validateFilters(filters, ERP_OUTSOURCED_BOM_DICTIONARY_CODE,
+                OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER, OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER);
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue(OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER,
+                requireTextFilter(filters, OUTSOURCED_PURCHASE_ORDER_NUMBER_FILTER));
+        parameters.addValue(OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER,
+                requireTextFilter(filters, OUTSOURCED_PURCHASE_ORDER_SEQUENCE_FILTER));
+        return queryPage(ERP_OUTSOURCED_BOM_DATA_SQL,
+                ERP_OUTSOURCED_BOM_COUNT_SQL, parameters, createPageRequest());
+    }
+
+    /**
+     * 创建所有数据字典共用的公司和据点数据库参数。
+     *
+     * @param filters 筛选条件
+     * @return 已绑定公司和据点的参数集合
+     */
+    private MapSqlParameterSource createBaseParameters(Map<String, String> filters) {
+        MapSqlParameterSource parameters = createCompanyParameters(filters);
+        parameters.addValue("t100id", requireTextFilter(filters, "t100id"));
+        return parameters;
+    }
+
+    /**
+     * 创建只依赖 ERP 公司编号的数据字典查询参数。
+     *
+     * @param filters 筛选条件集合
+     * @return 已绑定公司编号的参数集合
+     */
+    private MapSqlParameterSource createCompanyParameters(Map<String, String> filters) {
+        MapSqlParameterSource parameters = new MapSqlParameterSource();
+        parameters.addValue("ent", requireCompanyEnt(filters));
+        return parameters;
+    }
+
+    /**
+     * 限制数据字典参数名称,避免未声明参数被误用。
+     *
+     * @param filters 待校验的筛选条件
+     * @param dictionaryCode 数据字典编码
+     * @param dictionaryFilterNames 当前字典允许的筛选字段
+     */
+    private void validateFilters(Map<String, String> filters, String dictionaryCode, String... dictionaryFilterNames) {
+        for (String filterName : filters.keySet()) {
+            if (!isCommonFilter(filterName) && !isDictionaryFilter(filterName, dictionaryFilterNames)) {
+                throw new IllegalArgumentException("Unsupported filter for " + dictionaryCode + ": " + filterName);
+            }
+        }
+    }
+
+    private boolean isCommonFilter(String filterName) {
+        return "t100id".equals(filterName) || "ent".equals(filterName);
+    }
+
+    private boolean isDictionaryFilter(String filterName, String... dictionaryFilterNames) {
+        for (String dictionaryFilterName : dictionaryFilterNames) {
+            if (dictionaryFilterName.equals(filterName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 读取必填文本筛选条件,并去除首尾空白。
+     *
+     * @param filters 筛选条件集合
+     * @param filterName 筛选条件名称
+     * @return 非空筛选条件值
+     */
+    private String requireTextFilter(Map<String, String> filters, String filterName) {
+        String value = filters.get(filterName);
+        if (value == null || value.trim().isEmpty()) {
+            throw new IllegalArgumentException(filterName + " must not be blank");
+        }
+        return value.trim();
+    }
+
+    /**
+     * 将逗号分隔的权限值解析为命名参数集合,供 SQL 的 IN 条件安全绑定。
+     *
+     * @param filters 调用方传入的筛选条件
+     * @param filterName 筛选条件名称
+     * @return 不包含空值的权限值列表
+     */
+    private List<String> requireCommaSeparatedFilter(Map<String, String> filters, String filterName) {
+        String value = requireTextFilter(filters, filterName);
+        List<String> values = new ArrayList<String>();
+        for (String item : value.split(",")) {
+            String trimmedItem = item.trim();
+            if (trimmedItem.isEmpty()) {
+                throw new IllegalArgumentException(filterName + " must not contain blank values");
+            }
+            values.add(trimmedItem);
+        }
+        return values;
+    }
+
+    /**
+     * 读取可选的模糊匹配条件;空字符串视为未提供条件。
+     *
+     * @param filters 筛选条件集合
+     * @param filterName 筛选条件名称
+     * @return 去除首尾空白后的筛选值,未提供时返回 null
+     */
+    private String optionalTextFilter(Map<String, String> filters, String filterName) {
+        String value = filters.get(filterName);
+        return value == null || value.trim().isEmpty() ? null : value.trim();
+    }
+
+    private String createLikeValue(String value) {
+        return "%" + value + "%";
+    }
+
+    /**
+     * 将调用方传入的公司名称映射为 ERP 使用的公司编号。
+     *
+     * @param filters 筛选条件集合
+     * @return ERP 公司编号
+     */
+    private Long requireCompanyEnt(Map<String, String> filters) {
+        String companyName = requireTextFilter(filters, "ent");
+        if (MINGLEI_COMPANY_NAME.equals(companyName)) {
+            return Long.valueOf(DOMESTIC_COMPANY_ENT);
+        }
+        if (VIETNAM_COMPANY_NAME.equals(companyName)) {
+            return Long.valueOf(VIETNAM_COMPANY_ENT);
+        }
+        throw new IllegalArgumentException("ent must be 明磊 or 越南");
+    }
+
+    /**
+     * 创建固定的查询范围,数据字典接口始终返回前 100 条记录。
+     *
+     * @return 固定的分页请求参数
+     */
+    private DictionaryPageRequest createPageRequest() {
+        return new DictionaryPageRequest(DEFAULT_PAGE, DEFAULT_PAGE_SIZE);
+    }
+
+    /**
+     * 分别执行记录数和当前页查询,并去除 Oracle 分页产生的行号列。
+     *
+     * @param dataSql 不含分页的记录查询 SQL
+     * @param countSql 记录数查询 SQL
+     * @param parameters 筛选参数
+     * @param pageRequest 分页请求参数
+     * @return 分页查询结果
+     */
+    private Map<String, Object> queryPage(String dataSql, String countSql, MapSqlParameterSource parameters,
+                                          DictionaryPageRequest pageRequest) {
+        NamedParameterJdbcTemplate jdbcTemplate = requireJdbcTemplate();
+        Long total = jdbcTemplate.queryForObject(countSql, parameters, Long.class);
+        MapSqlParameterSource pageParameters = new MapSqlParameterSource(parameters.getValues());
+        pageParameters.addValue("offset", pageRequest.getOffset());
+        pageParameters.addValue("endRow", pageRequest.getEndRow());
+        List<Map<String, Object>> pageRows = jdbcTemplate.queryForList(createOraclePageSql(dataSql), pageParameters);
+
+        List<Map<String, Object>> records = new ArrayList<Map<String, Object>>();
+        for (Map<String, Object> pageRow : pageRows) {
+            Map<String, Object> record = new LinkedHashMap<String, Object>(pageRow);
+            record.remove(PAGE_ROW_NUMBER_COLUMN);
+            record.remove(PAGE_ROW_NUMBER_COLUMN.toLowerCase());
+            records.add(record);
+        }
+
+        Map<String, Object> result = new LinkedHashMap<String, Object>();
+        result.put("page", Integer.valueOf(pageRequest.getPage()));
+        result.put("pageSize", Integer.valueOf(pageRequest.getPageSize()));
+        result.put("total", total == null ? Long.valueOf(0L) : total);
+        result.put("records", records);
+        return result;
+    }
+
+    /**
+     * 使用 ROWNUM 生成兼容 Oracle 11g 的分页 SQL。
+     *
+     * @param dataSql 不含分页的记录查询 SQL
+     * @return 包含 offset 和 endRow 参数的分页 SQL
+     */
+    private String createOraclePageSql(String dataSql) {
+        return "select * from (select dictionary_page_data.*, rownum " + PAGE_ROW_NUMBER_COLUMN +
+                " from (" + dataSql + ") dictionary_page_data where rownum <= :endRow) " +
+                "where " + PAGE_ROW_NUMBER_COLUMN + " > :offset";
+    }
+
+    /**
+     * 获取已由 Oracle 数据源配置创建的具名参数 JDBC 模板。
+     *
+     * @return 数据字典 JDBC 模板
+     */
+    private NamedParameterJdbcTemplate requireJdbcTemplate() {
+        NamedParameterJdbcTemplate jdbcTemplate = jdbcTemplateProvider.getIfAvailable();
+        if (jdbcTemplate == null) {
+            throw new IllegalStateException("MINGLEI_DICTIONARY_DATASOURCE_URL is not configured");
+        }
+        return jdbcTemplate;
+    }
+
+    /**
+     * 封装经过校验的分页参数,避免将分页计算分散到各字典 SQL 分支。
+     */
+    private static class DictionaryPageRequest {
+
+        private final int page;
+        private final int pageSize;
+
+        private DictionaryPageRequest(int page, int pageSize) {
+            this.page = page;
+            this.pageSize = pageSize;
+        }
+
+        private int getPage() {
+            return page;
+        }
+
+        private int getPageSize() {
+            return pageSize;
+        }
+
+        private long getOffset() {
+            return (long) (page - 1) * pageSize;
+        }
+
+        private long getEndRow() {
+            return getOffset() + pageSize;
+        }
+    }
+}