feat: 在查询元数据键中添加 _where 支持,并实现自定义过滤器功能

This commit is contained in:
Jafeng 2026-06-24 14:55:21 +08:00
parent 756213f9af
commit 121a46ce33
4 changed files with 90 additions and 6 deletions

View File

@ -479,7 +479,6 @@ function mapOutpatientVisit(row, options = {}) {
const registerType = pick(row, ["ST_REG_TYPE_NAME", "ST_REG_CATEGORY_NAME"]);
const visitType = pick(row, ["VISIT_TYPE", "ST_VISIT_TYPE"]);
const chargeType = pick(row, ["ST_CHARGE_TYPE", "SC_CHARGE_TYPE"]);
const serviceItems = joinPicked(row, ["ST_REG_TYPE_NAME", "ST_REG_CATEGORY_NAME", "ST_VISIT_TYPE", "VISIT_TYPE", "ST_CHARGE_TYPE"]);
const amount = pick(row, [
"TREATMENT_COST",
"REGISTER_TOTAL_COST",
@ -522,8 +521,6 @@ function mapOutpatientVisit(row, options = {}) {
visitType,
chargeType,
insuranceType: pick(row, ["INSURANCE_TYPE"]),
serviceItems,
serviceItem: serviceItems,
amount,
treatmentCost: pick(row, ["TREATMENT_COST"]),
registerCost: pick(row, ["REGISTER_COST"]),

View File

@ -2,7 +2,7 @@ const sql = require("mssql");
const { getConfig, parsePositiveInteger } = require("../../core/config");
const pools = new Map();
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot"]);
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot", "_where"]);
const RANGE_FILTERS = {
startCreateTime: { field: "create_time", op: ">=" },
endCreateTime: { field: "create_time", op: "<=" },
@ -97,6 +97,7 @@ function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
const standardFields = new Set(fields);
const inputs = [];
const clauses = [];
addCustomFilters({ clauses, inputs, where: query && query._where, standardFields, fieldMapping });
for (const [key, rawValue] of Object.entries(query || {})) {
if (QUERY_META_KEYS.has(key) || isEmpty(rawValue)) continue;
@ -123,6 +124,14 @@ function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
function addFilter({ clauses, inputs, standardField, value, fieldMapping, op }) {
const sourceField = assertIdentifier(fieldMapping[standardField] || standardField, `filter.${standardField}`);
const paramName = `p${inputs.length}`;
if (op === "notEmpty") {
clauses.push(`(${quoteName(sourceField)} IS NOT NULL AND LTRIM(RTRIM(CONVERT(NVARCHAR(MAX), ${quoteName(sourceField)}))) <> '')`);
return;
}
if (op === "isEmpty") {
clauses.push(`(${quoteName(sourceField)} IS NULL OR LTRIM(RTRIM(CONVERT(NVARCHAR(MAX), ${quoteName(sourceField)}))) = '')`);
return;
}
if (op === "like") {
clauses.push(`${quoteName(sourceField)} LIKE @${paramName}`);
inputs.push({ name: paramName, value: `%${String(value).trim()}%` });
@ -132,6 +141,28 @@ function addFilter({ clauses, inputs, standardField, value, fieldMapping, op })
inputs.push({ name: paramName, value });
}
function addCustomFilters({ clauses, inputs, where, standardFields, fieldMapping }) {
if (!Array.isArray(where)) return;
for (const item of where) {
const standardField = item && String(item.field || item.column || "").trim();
if (!standardField || !standardFields.has(standardField)) continue;
const op = normalizeCustomOperator(item.op || item.operator || item.type);
if (!op) continue;
if (op !== "notEmpty" && op !== "isEmpty" && isEmpty(item.value)) continue;
addFilter({ clauses, inputs, standardField, value: item.value, fieldMapping, op });
}
}
function normalizeCustomOperator(op) {
const text = String(op || "eq").trim().toLowerCase();
if (["notempty", "not_empty", "isnotempty", "is_not_empty"].includes(text)) return "notEmpty";
if (["isempty", "is_empty", "empty"].includes(text)) return "isEmpty";
if (["like", "contains"].includes(text)) return "like";
if (["ne", "neq", "!=", "<>"].includes(text)) return "<>";
if (["eq", "="].includes(text)) return "=";
return "";
}
function getFilterOperator(resourceConfig, standardField) {
const configured = resourceConfig.filterOperators && resourceConfig.filterOperators[standardField];
if (!configured) return "=";

View File

@ -2,7 +2,7 @@ const { spawn } = require("child_process");
const iconv = require("iconv-lite");
const { getConfig, parsePositiveInteger } = require("../../core/config");
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot"]);
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot", "_where"]);
const RANGE_FILTERS = {
startCreateTime: { field: "create_time", op: ">=" },
endCreateTime: { field: "create_time", op: "<=" },
@ -182,6 +182,7 @@ function parseSqlplusOutput(output, fields) {
function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
const standardFields = new Set(fields);
const clauses = [];
addCustomFilters({ clauses, where: query && query._where, standardFields, fieldMapping });
for (const [key, rawValue] of Object.entries(query || {})) {
if (QUERY_META_KEYS.has(key) || isEmpty(rawValue)) continue;
@ -202,10 +203,34 @@ function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
function buildFilterSql({ standardField, value, fieldMapping, op }) {
const sourceField = assertIdentifier(fieldMapping[standardField] || standardField, `filter.${standardField}`);
if (op === "notEmpty") return `${cleanSqlValue(identifier(sourceField))} <> ''`;
if (op === "isEmpty") return `${cleanSqlValue(identifier(sourceField))} = ''`;
if (op === "like") return `${cleanSqlValue(identifier(sourceField))} LIKE ${literal(`%${String(value).trim()}%`)}`;
return `${cleanSqlValue(identifier(sourceField))} ${op || "="} ${literal(value)}`;
}
function addCustomFilters({ clauses, where, standardFields, fieldMapping }) {
if (!Array.isArray(where)) return;
for (const item of where) {
const standardField = item && String(item.field || item.column || "").trim();
if (!standardField || !standardFields.has(standardField)) continue;
const op = normalizeCustomOperator(item.op || item.operator || item.type);
if (!op) continue;
if (op !== "notEmpty" && op !== "isEmpty" && isEmpty(item.value)) continue;
clauses.push(buildFilterSql({ standardField, value: item.value, fieldMapping, op }));
}
}
function normalizeCustomOperator(op) {
const text = String(op || "eq").trim().toLowerCase();
if (["notempty", "not_empty", "isnotempty", "is_not_empty"].includes(text)) return "notEmpty";
if (["isempty", "is_empty", "empty"].includes(text)) return "isEmpty";
if (["like", "contains"].includes(text)) return "like";
if (["ne", "neq", "!=", "<>"].includes(text)) return "<>";
if (["eq", "="].includes(text)) return "=";
return "";
}
function getFilterOperator(resourceConfig, standardField) {
const configured = resourceConfig.filterOperators && resourceConfig.filterOperators[standardField];
if (!configured) return "=";

View File

@ -4,7 +4,7 @@ const { getConfig, parsePositiveInteger } = require("../../core/config");
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
const pools = new Map();
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot"]);
const QUERY_META_KEYS = new Set(["page", "pageSize", "saveSnapshot", "_where"]);
const RANGE_FILTERS = {
startCreateTime: { field: "create_time", op: ">=" },
endCreateTime: { field: "create_time", op: "<=" },
@ -96,6 +96,7 @@ function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
const standardFields = new Set(fields);
const binds = {};
const clauses = [];
addCustomFilters({ clauses, binds, where: query && query._where, standardFields, fieldMapping });
for (const [key, rawValue] of Object.entries(query || {})) {
if (QUERY_META_KEYS.has(key) || isEmpty(rawValue)) continue;
@ -122,6 +123,14 @@ function buildWhereClause({ query, fields, fieldMapping, resourceConfig }) {
function addFilter({ clauses, binds, standardField, value, fieldMapping, op }) {
const sourceField = assertIdentifier(fieldMapping[standardField] || standardField, `filter.${standardField}`);
const paramName = `p${Object.keys(binds).length}`;
if (op === "notEmpty") {
clauses.push(`((${identifier(sourceField)} IS NOT NULL) AND TRIM(TO_CHAR(${identifier(sourceField)})) <> '')`);
return;
}
if (op === "isEmpty") {
clauses.push(`((${identifier(sourceField)} IS NULL) OR TRIM(TO_CHAR(${identifier(sourceField)})) = '')`);
return;
}
if (op === "like") {
clauses.push(`${identifier(sourceField)} LIKE :${paramName}`);
binds[paramName] = `%${String(value).trim()}%`;
@ -131,6 +140,28 @@ function addFilter({ clauses, binds, standardField, value, fieldMapping, op }) {
binds[paramName] = value;
}
function addCustomFilters({ clauses, binds, where, standardFields, fieldMapping }) {
if (!Array.isArray(where)) return;
for (const item of where) {
const standardField = item && String(item.field || item.column || "").trim();
if (!standardField || !standardFields.has(standardField)) continue;
const op = normalizeCustomOperator(item.op || item.operator || item.type);
if (!op) continue;
if (op !== "notEmpty" && op !== "isEmpty" && isEmpty(item.value)) continue;
addFilter({ clauses, binds, standardField, value: item.value, fieldMapping, op });
}
}
function normalizeCustomOperator(op) {
const text = String(op || "eq").trim().toLowerCase();
if (["notempty", "not_empty", "isnotempty", "is_not_empty"].includes(text)) return "notEmpty";
if (["isempty", "is_empty", "empty"].includes(text)) return "isEmpty";
if (["like", "contains"].includes(text)) return "like";
if (["ne", "neq", "!=", "<>"].includes(text)) return "<>";
if (["eq", "="].includes(text)) return "=";
return "";
}
function getFilterOperator(resourceConfig, standardField) {
const configured = resourceConfig.filterOperators && resourceConfig.filterOperators[standardField];
if (!configured) return "=";