@opengis/fastify-table 1.2.73 → 1.2.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/plugins/table/funcs/getFilterSQL/index.js +8 -9
- package/server/plugins/table/funcs/getFilterSQL/util/formatValue.js +67 -211
- package/server/plugins/table/funcs/getFilterSQL/util/getFilterQuery.js +4 -4
- package/server/plugins/table/funcs/getFilterSQL/util/getRangeQuery.js +154 -0
- package/server/routes/table/controllers/filter.js +12 -20
package/package.json
CHANGED
|
@@ -11,11 +11,11 @@ const checkInline = {};
|
|
|
11
11
|
const defaultTable = 'crm.extra_data';
|
|
12
12
|
|
|
13
13
|
function getExtraQuery(mainColumns, schema, table, pk) {
|
|
14
|
-
const
|
|
14
|
+
const extraColumns = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key)).map(key => ({ name: key, type: schema[key]?.type }));
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
|
-
q:
|
|
18
|
-
|
|
17
|
+
q: extraColumns.map((el) => `,(select value_text as "${el.name.replace(/'/g, "''")}" from ${table} where property_key='${el.name.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
|
|
18
|
+
extraColumns,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -37,7 +37,7 @@ export default async function getFilterSQL({
|
|
|
37
37
|
: undefined;
|
|
38
38
|
|
|
39
39
|
const { fields: fieldsModel = [] } = body?.table && pg.pk[body.table] ? await pg.query(`select * from ${body.table} limit 0`) : {};
|
|
40
|
-
const { q: extraSqlColumns,
|
|
40
|
+
const { q: extraSqlColumns, extraColumns = [] } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
|
|
41
41
|
? getExtraQuery(fieldsModel, loadTemplate?.schema, extraDataTable, pg.pk[body.table || table])
|
|
42
42
|
: {};
|
|
43
43
|
|
|
@@ -63,10 +63,10 @@ export default async function getFilterSQL({
|
|
|
63
63
|
.map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', uid)}) ${el.name || `t${i}`} on 1=1 `)
|
|
64
64
|
.join('')
|
|
65
65
|
: null;
|
|
66
|
-
const sqlInline = body
|
|
66
|
+
const sqlInline = body?.sql?.filter?.(el => el.inline).map(el => `,(${el.sql})`).join('');
|
|
67
67
|
|
|
68
68
|
const fieldQuery = `select * ${extraSqlColumns || ''} ${sqlInline || ''} from ${body?.table || table} t ${sqlTable || ''} limit 0`;
|
|
69
|
-
const { fields = [] } =
|
|
69
|
+
const { fields = [] } = await pg.query(fieldQuery);
|
|
70
70
|
const autoSearchColumn = fields?.filter((el) => pg.pgType?.[el.dataTypeID] === 'text')?.map((el) => el.name).join(',');
|
|
71
71
|
const searchColumn = body?.search_column || body?.meta?.search || autoSearchColumn;
|
|
72
72
|
const fieldsList = (fieldsModel || fields)?.map((el) => el.name);
|
|
@@ -92,8 +92,8 @@ export default async function getFilterSQL({
|
|
|
92
92
|
)?.filter(el => el.id || el.name)?.map(async (el) => {
|
|
93
93
|
el.name = el.name || el.id;
|
|
94
94
|
|
|
95
|
-
if (el.name &&
|
|
96
|
-
Object.assign(el, { extra: { table: extraDataTable } });
|
|
95
|
+
if (el.name && extraColumns.find(item => item?.name === el?.name)) {
|
|
96
|
+
Object.assign(el, { extra: { table: extraDataTable, input: extraColumns.find(item => item?.name === el?.name)?.type } });
|
|
97
97
|
}
|
|
98
98
|
if (!el?.data) return el;
|
|
99
99
|
const cls = await getTemplate(['cls', 'select'], el.data);
|
|
@@ -139,7 +139,6 @@ export default async function getFilterSQL({
|
|
|
139
139
|
optimizedSQL,
|
|
140
140
|
sqlTable,
|
|
141
141
|
extraSqlColumns,
|
|
142
|
-
extraKeys,
|
|
143
142
|
tableCount,
|
|
144
143
|
table: modelQuery,
|
|
145
144
|
// filter parts
|
|
@@ -1,199 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer'];
|
|
1
|
+
import getRangeQuery from "./getRangeQuery.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// iso date: 2024-01-01
|
|
7
|
-
if (dateStr?.indexOf('-') !== -1) {
|
|
8
|
-
const [yyyy, mm, dd] = dateStr.split('-');
|
|
9
|
-
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// locale date: 01.01.2024
|
|
13
|
-
const [dd, mm, yyyy] = dateStr.split('.');
|
|
14
|
-
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function dt(y, m, d) {
|
|
18
|
-
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const dp = {
|
|
22
|
-
d: new Date().getDate(),
|
|
23
|
-
w: new Date().getDate() - (new Date().getDay() || 7) + 1,
|
|
24
|
-
m: new Date().getMonth(),
|
|
25
|
-
q: (new Date().getMonth() / 4).toFixed() * 3,
|
|
26
|
-
y: new Date().getFullYear(),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function formatDateISOString(date) {
|
|
30
|
-
if (!date?.includes('.')) return date;
|
|
31
|
-
const [day, month, year] = date.split('.');
|
|
32
|
-
return `${year}-${month}-${day}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getDates(value) {
|
|
36
|
-
// date range, specific options: current day, week, month, year etc.
|
|
37
|
-
if (value === 'cd') {
|
|
38
|
-
return [dt(dp.y, dp.m, dp.d)];
|
|
39
|
-
}
|
|
40
|
-
if (value === 'cw') {
|
|
41
|
-
return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
|
|
42
|
-
}
|
|
43
|
-
if (value === 'cm') {
|
|
44
|
-
return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
|
|
45
|
-
}
|
|
46
|
-
if (value === 'cq') {
|
|
47
|
-
return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
|
|
48
|
-
}
|
|
49
|
-
if (value === 'cy') {
|
|
50
|
-
return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// date range, example: 01.01.2024-31.12.2024
|
|
54
|
-
const [startDate, endDate] = value.split('-');
|
|
55
|
-
const min = formatDateISOString(startDate);
|
|
56
|
-
const max = formatDateISOString(endDate);
|
|
57
|
-
return [min, max];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function formatValue({
|
|
61
|
-
pg, table, filter = {}, name, value, operator = '=', dataTypeID, uid = 1, optimize,
|
|
3
|
+
export default function formatValue({
|
|
4
|
+
pg, table, filter = {}, name, value, dataTypeID, uid = 1, optimize,
|
|
62
5
|
}) {
|
|
63
|
-
const { extra, sql, select } = filter;
|
|
6
|
+
const { extra, sql, select, strict } = filter;
|
|
64
7
|
const pk = pg?.pk && table ? pg.pk[table] : undefined;
|
|
65
8
|
|
|
66
|
-
if (!dataTypeID && !extra) return {};
|
|
67
|
-
|
|
68
9
|
const filterType = filter.type?.toLowerCase() || 'text';
|
|
69
|
-
const fieldType = pg
|
|
10
|
+
const fieldType = pg?.pgType?.[dataTypeID] || pg?.pgType?.[{ date: 1114 }[filterType] || 25];
|
|
70
11
|
|
|
71
|
-
if (!name
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// check date is valid
|
|
78
|
-
if (!isValidDate(min) || (!isValidDate(max) && value !== 'cd')) {
|
|
79
|
-
return { op: 'between', query: 'false' };
|
|
80
|
-
}
|
|
12
|
+
if (!name
|
|
13
|
+
|| !value
|
|
14
|
+
|| !dataTypeID && !extra && !sql || extra?.input?.toLowerCase?.() === 'datatable') {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
81
17
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
18
|
+
// my rows
|
|
19
|
+
if (value === 'me' && uid) {
|
|
20
|
+
return { op: '=', query: `${name}::text = '${uid}'` };
|
|
21
|
+
}
|
|
86
22
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
23
|
+
const matchNull = { null: 'is null', notnull: 'is not null' }[value];
|
|
24
|
+
// null = true is null, not false
|
|
25
|
+
const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
|
|
26
|
+
const matchMulti = fieldType.includes('[]') ? `::text[] && '{${value}}'::text[]` : null;
|
|
27
|
+
const match = matchNull || matchBoolean || matchMulti || `::text=any('{${value}}'::text[])`;
|
|
91
28
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
29
|
+
// geometry
|
|
30
|
+
if (['geometry'].includes(fieldType)) {
|
|
31
|
+
if (matchNull) {
|
|
32
|
+
return { op: 'nullable', query: `${name} ${matchNull}` };
|
|
95
33
|
}
|
|
96
34
|
|
|
97
|
-
|
|
98
|
-
if (extra?.table && pk) {
|
|
99
|
-
const query = extra?.table && pk
|
|
100
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_date::date between '${min}'::date and '${max}'::date)`
|
|
101
|
-
: `${pk} in (select ${pk} from ${extra.table} where ${name}::date between '${min}'::date and '${max}'::date)`;
|
|
102
|
-
return { op: 'between', query };
|
|
103
|
-
}
|
|
35
|
+
const bbox = value[0].split('_');
|
|
104
36
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return { op: 'strict', query: `${name}::date = '${min}'::date` };
|
|
37
|
+
if (bbox?.length !== 4) {
|
|
38
|
+
return {};
|
|
108
39
|
}
|
|
109
40
|
|
|
110
|
-
|
|
111
|
-
return { op: '
|
|
41
|
+
const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
|
|
42
|
+
return { op: '&&', query };
|
|
112
43
|
}
|
|
113
44
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const [min, max] = value?.indexOf('_') !== -1 ? value.split('_') : value.split('-');
|
|
45
|
+
const querysql = sql?.replace?.(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`)?.replace?.(/\$1/g, `'${value}'`);
|
|
46
|
+
const op = filterType === 'text' && !strict ? '~' : '=';
|
|
117
47
|
|
|
48
|
+
// text
|
|
49
|
+
if (['text'].includes(filterType)) {
|
|
118
50
|
// with sql subquery
|
|
119
51
|
if (sql) {
|
|
120
|
-
return { op: '=', query:
|
|
52
|
+
return { op: '=', query: querysql };
|
|
121
53
|
}
|
|
122
54
|
|
|
123
|
-
|
|
124
|
-
|
|
55
|
+
// extra table, example: crm.extra_data
|
|
56
|
+
if (extra?.table && pk) {
|
|
57
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `${op} '${value}'`} )`;
|
|
58
|
+
return { op, query };
|
|
125
59
|
}
|
|
126
60
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
61
|
+
// select / cls
|
|
62
|
+
if (select) {
|
|
63
|
+
const subquery = matchNull ? `name ${matchNull}` : `lower(name::text) ~ lower('${value}')`;
|
|
64
|
+
return {
|
|
65
|
+
op,
|
|
66
|
+
query: `${name}::text in ( ( with q(id,name) as (${select}) select id from q where ${subquery}) )`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
131
69
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const query = sql.replace(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`);
|
|
137
|
-
return { op: '=', query };
|
|
70
|
+
// nullable
|
|
71
|
+
if (matchNull) {
|
|
72
|
+
const query = `${name} ${matchNull}`;
|
|
73
|
+
return { op, query };
|
|
138
74
|
}
|
|
139
75
|
|
|
140
76
|
// default
|
|
141
|
-
const query =
|
|
142
|
-
return { op: '=', query };
|
|
143
|
-
}
|
|
77
|
+
const query = `lower(${name}::text) ${op} lower('${value}')`;
|
|
144
78
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const matchMulti = value?.indexOf(',') !== -1 ? `= any('{${value}}'::text[])` : null;
|
|
148
|
-
const match = matchNull || matchBoolean || matchMulti || (operator === '=' ? `='${value}'` : `ilike '%${value}%'`);
|
|
79
|
+
return { op, query };
|
|
80
|
+
}
|
|
149
81
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
int8: 'numeric',
|
|
156
|
-
"double precision": 'double precision',
|
|
157
|
-
varchar: 'text',
|
|
158
|
-
"character varying": 'character varying',
|
|
159
|
-
bool: 'boolean',
|
|
160
|
-
boolean: 'boolean',
|
|
161
|
-
geometry: 'geom',
|
|
162
|
-
}[fieldType] || 'text';
|
|
82
|
+
// date: 01.01.2024-31.12.2024 / number range: 100-500
|
|
83
|
+
if (['date', 'datepicker', 'range'].includes(filterType)) {
|
|
84
|
+
const query = getRangeQuery(value, name, fieldType, filterType, sql, extra, pk);
|
|
85
|
+
return { op: 'between', query };
|
|
86
|
+
}
|
|
163
87
|
|
|
164
88
|
/* select query - from admin.cls / filter options */
|
|
165
89
|
if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
|
|
90
|
+
// with sql subquery
|
|
91
|
+
if (sql) {
|
|
92
|
+
return { op: '=', query: querysql };
|
|
93
|
+
}
|
|
166
94
|
|
|
167
95
|
// extra table, example: crm.extra_data
|
|
168
96
|
if (extra?.table && pk) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${subquery} )`
|
|
172
|
-
: `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${match})`;
|
|
173
|
-
return { op: operator, query };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// default
|
|
177
|
-
const query = select && filterType === 'text'
|
|
178
|
-
? `${name}::text in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}) )` // filter with cls
|
|
179
|
-
: `${name}::${formatType} ${match}`; // simple filter
|
|
180
|
-
return { op: operator, query };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// my rows
|
|
184
|
-
if (value === 'me' && uid) {
|
|
185
|
-
if (sql) {
|
|
186
|
-
return { op: '=', query: sql.replace(/\$1/g, `'${uid}'`) };
|
|
97
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `='${value}'`} )`;
|
|
98
|
+
return { op: '=', query };
|
|
187
99
|
}
|
|
188
100
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
: `${name}::text = '${uid}'`;
|
|
101
|
+
// nullable + default
|
|
102
|
+
const query = `${name} ${match}`;
|
|
192
103
|
return { op: '=', query };
|
|
193
104
|
}
|
|
194
105
|
|
|
195
|
-
// sql column
|
|
196
|
-
if (optimize && optimize.name !== optimize.pk) {
|
|
106
|
+
// sql column
|
|
107
|
+
if (optimize && optimize.name !== optimize.pk && false) {
|
|
197
108
|
const val = filterType === 'text' ? `ilike '%${value}%'` : `= any('{${value}}')`;
|
|
198
109
|
return {
|
|
199
110
|
op: '~',
|
|
@@ -204,60 +115,5 @@ function formatValue({
|
|
|
204
115
|
};
|
|
205
116
|
}
|
|
206
117
|
|
|
207
|
-
// filter text array column
|
|
208
|
-
if (fieldType?.includes('[]')) {
|
|
209
|
-
return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// filter multiple values, example: 1,2,3
|
|
213
|
-
if (value?.indexOf(',') !== -1) {
|
|
214
|
-
const values = value.split(',').filter((el) => el !== 'null');
|
|
215
|
-
|
|
216
|
-
// extra table, example: crm.extra_data
|
|
217
|
-
if (extra?.table && pk) {
|
|
218
|
-
const query = value?.indexOf('null') !== -1
|
|
219
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ( value_text is null or value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) ) )`
|
|
220
|
-
: `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
|
|
221
|
-
return { op: 'in', query };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// default
|
|
225
|
-
const query = value?.indexOf('null') !== -1
|
|
226
|
-
? `( ${name} is null or ${name}::text in (${values?.map((el) => `'${el}'`).join(',')}) )`
|
|
227
|
-
: `${name}::text in (${value.split(',')?.map((el) => `'${el}'`).join(',')})`;
|
|
228
|
-
return { op: 'in', query };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (['<', '>'].includes(operator)) {
|
|
232
|
-
const query = `${name} ${operator} '${value}'::${formatType}`;
|
|
233
|
-
return { op: operator, query };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (operator === '=' && filterType !== 'text' && !filter?.data) {
|
|
237
|
-
const query = {
|
|
238
|
-
null: `${name} is null`,
|
|
239
|
-
notnull: `${name} is not null`,
|
|
240
|
-
}[value] || `${name}::${formatType}='${value}'::${formatType}`;
|
|
241
|
-
return { op: '=', query };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// json
|
|
245
|
-
if (name.includes('.')) {
|
|
246
|
-
const [col, prop] = name.split('.');
|
|
247
|
-
const query = ` ${col}->>'${prop}' in ('${value.join("','")}')`;
|
|
248
|
-
return { op: 'in', query };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// geometry
|
|
252
|
-
if (['geometry'].includes(fieldType)) {
|
|
253
|
-
const bbox = value[0].split('_');
|
|
254
|
-
|
|
255
|
-
if (bbox?.length === 4) {
|
|
256
|
-
const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
|
|
257
|
-
return { op: '&&', query };
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
118
|
return {};
|
|
261
119
|
}
|
|
262
|
-
|
|
263
|
-
export default formatValue;
|
|
@@ -32,7 +32,6 @@ function getQuery({
|
|
|
32
32
|
// filter
|
|
33
33
|
const filter = filterList?.find?.((el) => el.type && el.name === name) || { type: 'text' };
|
|
34
34
|
const { strict, extra } = filter;
|
|
35
|
-
const operator = filter.strict || ['check', 'autocomplete'].includes(filter.type.toLowerCase()) ? '=' : (splitby !== '=' ? splitby : '~');
|
|
36
35
|
|
|
37
36
|
// find all value
|
|
38
37
|
const value = filterQueryArray.filter((el) => el.split(splitby)?.[0] === name).map((el) => el.substring(name.length + 1)).join(',');
|
|
@@ -40,7 +39,9 @@ function getQuery({
|
|
|
40
39
|
const optimize = fields?.find((el) => el.name === name) ? null : tableSQL.find((el) => el.name === name);
|
|
41
40
|
|
|
42
41
|
// find field and skip not exists
|
|
43
|
-
const { dataTypeID } = fields?.find((el) => el.name === name)
|
|
42
|
+
const { dataTypeID } = fields?.find((el) => el.name === name)
|
|
43
|
+
|| fields?.find((el) => el.name === optimize?.pk)
|
|
44
|
+
|| {};
|
|
44
45
|
|
|
45
46
|
// format query
|
|
46
47
|
const {
|
|
@@ -49,12 +50,11 @@ function getQuery({
|
|
|
49
50
|
pg,
|
|
50
51
|
table,
|
|
51
52
|
filter,
|
|
52
|
-
optimize,
|
|
53
53
|
name,
|
|
54
54
|
value,
|
|
55
|
-
operator,
|
|
56
55
|
dataTypeID,
|
|
57
56
|
uid,
|
|
57
|
+
optimize,
|
|
58
58
|
}) || {};
|
|
59
59
|
if (!query) continue;
|
|
60
60
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
|
|
2
|
+
const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer', 'bigint'];
|
|
3
|
+
|
|
4
|
+
const isValidDate = (dateStr) => {
|
|
5
|
+
if (!dateStr) return false;
|
|
6
|
+
if (['min', 'max'].includes(dateStr)) return true;
|
|
7
|
+
// iso date: 2024-01-01
|
|
8
|
+
if (dateStr?.indexOf('-') !== -1) {
|
|
9
|
+
const [yyyy, mm, dd] = dateStr.split('-');
|
|
10
|
+
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// locale date: 01.01.2024
|
|
14
|
+
const [dd, mm, yyyy] = dateStr.split('.');
|
|
15
|
+
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const isValidNumber = (numStr) => ['min', 'max'].includes(numStr) ? true : !isNaN(numStr);
|
|
19
|
+
|
|
20
|
+
function dt(y, m, d) {
|
|
21
|
+
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dp = {
|
|
25
|
+
d: new Date().getDate(),
|
|
26
|
+
w: new Date().getDate() - (new Date().getDay() || 7) + 1,
|
|
27
|
+
m: new Date().getMonth(),
|
|
28
|
+
q: (new Date().getMonth() / 4).toFixed() * 3,
|
|
29
|
+
y: new Date().getFullYear(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function formatDateISOString(date) {
|
|
33
|
+
if (!date?.includes('.')) return date;
|
|
34
|
+
const [day, month, year] = date.split('.');
|
|
35
|
+
return `${year}-${month}-${day}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getRangeValues(value = '', filterType = 'range') {
|
|
39
|
+
if (['range'].includes(filterType)) {
|
|
40
|
+
return value?.indexOf('_') !== -1
|
|
41
|
+
? value.split('_')
|
|
42
|
+
: value.split('-');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// date range, specific options: current day, week, month, year etc.
|
|
46
|
+
if (value === 'cd') {
|
|
47
|
+
return [dt(dp.y, dp.m, dp.d)];
|
|
48
|
+
}
|
|
49
|
+
if (value === 'cw') {
|
|
50
|
+
return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
|
|
51
|
+
}
|
|
52
|
+
if (value === 'cm') {
|
|
53
|
+
return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
|
|
54
|
+
}
|
|
55
|
+
if (value === 'cq') {
|
|
56
|
+
return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
|
|
57
|
+
}
|
|
58
|
+
if (value === 'cy') {
|
|
59
|
+
return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// date range, example: 01.01.2024-31.12.2024
|
|
63
|
+
const [startDate, endDate] = value.split('-');
|
|
64
|
+
const min = startDate === 'min' ? startDate : formatDateISOString(startDate);
|
|
65
|
+
const max = endDate === 'max' ? endDate : formatDateISOString(endDate);
|
|
66
|
+
return [min, max];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkValid(value, fieldType, filterType, sql, extra) {
|
|
70
|
+
const [min, max] = getRangeValues(value, filterType);
|
|
71
|
+
|
|
72
|
+
if (['date', 'datepicker'].includes(filterType)) {
|
|
73
|
+
const isvalid = (dateTypeList.includes(fieldType) || sql || extra) && (isValidDate(min) && (isValidDate(max) || value === 'cd'));
|
|
74
|
+
return { min, max, isvalid };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (['range'].includes(filterType)) {
|
|
78
|
+
const isvalid = (numberTypeList.includes(fieldType) || sql || extra) && isValidNumber(min) && isValidNumber(max);
|
|
79
|
+
return { min, max, isvalid };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { isvalid: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function getRangeQuery(value, name, fieldType, filterType, sql, extra, pk) {
|
|
86
|
+
const { min, max, isvalid } = checkValid(value, fieldType, filterType, sql, extra);
|
|
87
|
+
if (!isvalid) return 'false';
|
|
88
|
+
|
|
89
|
+
// with sql subquery
|
|
90
|
+
if (['date', 'datepicker'].includes(filterType) && sql) {
|
|
91
|
+
return sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// with sql subquery
|
|
95
|
+
if (['range'].includes(filterType) && sql) {
|
|
96
|
+
return sql.replace(/\$1/g, min).replace(/\$2/g, max);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// preset date values
|
|
100
|
+
if (['date', 'datepicker'].includes(filterType) && ['cd', 'cw', 'cm', 'cq', 'cy'].includes(value)) {
|
|
101
|
+
return value === 'cd'
|
|
102
|
+
? `${name}::date = '${min}'::date`
|
|
103
|
+
: `${name}::date between '${min}'::date and '${max}'::date`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// extra columns, example: crm.extra_data
|
|
107
|
+
if (['date', 'datepicker'].includes(filterType) && extra?.table && pk) {
|
|
108
|
+
const subquery = (max && min && min === max ? `value_date = '${value}'::date` : null)
|
|
109
|
+
|| (max === 'max' ? `value_date::date > '${min}'::date` : null)
|
|
110
|
+
|| (min === 'min' ? `value_date::date < '${max}'::date` : null)
|
|
111
|
+
|| `value_date::date between '${min}'::date and '${max}'::date`;
|
|
112
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery})`;
|
|
113
|
+
return query;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// extra columns, example: crm.extra_data
|
|
117
|
+
if (['range'].includes(filterType) && extra?.table && pk) {
|
|
118
|
+
const subquery = (max && min && min === max ? `value_text = '${value}'` : null)
|
|
119
|
+
|| (max === 'max' ? `value_text::numeric > ${min}` : null)
|
|
120
|
+
|| (min === 'min' ? `value_text::numeric < ${max}` : null)
|
|
121
|
+
|| `value_text::numeric between ${min} and ${max}`;
|
|
122
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery} )`;
|
|
123
|
+
return query;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// same date
|
|
127
|
+
if (['date', 'datepicker'].includes(filterType) && max && min && min === max) {
|
|
128
|
+
return `${name}::date = '${min}'::date`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// skip, same as 1=1
|
|
132
|
+
if (['range'].includes(filterType) && min === 'min' && max === 'max') {
|
|
133
|
+
return 'true';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// default
|
|
137
|
+
if (max === 'max') {
|
|
138
|
+
return ['date', 'datepicker'].includes(filterType)
|
|
139
|
+
? `${name} > '${min}'::date`
|
|
140
|
+
: `${name} > ${min}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (min === 'min') {
|
|
144
|
+
return ['date', 'datepicker'].includes(filterType)
|
|
145
|
+
? `${name} < '${max}'::date`
|
|
146
|
+
: `${name} < ${max}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (['date', 'datepicker'].includes(filterType)) {
|
|
150
|
+
return `${name}::date between '${min}'::date and '${max}'::date`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `${name} between ${min} and ${max}`;
|
|
154
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { logger, autoIndex, getSelect,
|
|
1
|
+
import { logger, autoIndex, getSelect, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
|
|
2
2
|
|
|
3
3
|
export default async function filterAPI(req) {
|
|
4
4
|
const time = Date.now();
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
|
-
params,
|
|
7
|
+
params, pg = pgClients.client, user = {},
|
|
8
8
|
} = req;
|
|
9
9
|
|
|
10
10
|
const loadTable = await getTemplate('table', params.table);
|
|
@@ -14,20 +14,7 @@ export default async function filterAPI(req) {
|
|
|
14
14
|
const { fields: columns = [] } = await pg.query(`select * from ${loadTable.table} t ${sqlTable} limit 0`);
|
|
15
15
|
const { fields = [] } = await pg.query(`select * from ${loadTable.table} t limit 0`);
|
|
16
16
|
|
|
17
|
-
const {
|
|
18
|
-
filter, custom, state, search,
|
|
19
|
-
} = query;
|
|
20
|
-
|
|
21
|
-
// const { extra } = loadTable?.form ? await getTemplate('form', loadTable?.form) || {} : {};
|
|
22
|
-
|
|
23
|
-
const { optimizedSQL = `select * from ${loadTable.table}` } = false ? await getFilterSQL({
|
|
24
|
-
pg,
|
|
25
|
-
table: params.table,
|
|
26
|
-
filter,
|
|
27
|
-
custom,
|
|
28
|
-
state,
|
|
29
|
-
search,
|
|
30
|
-
}) : {};
|
|
17
|
+
const optimizedSQL = `select * from ${loadTable.table} t ${sqlTable}`;
|
|
31
18
|
|
|
32
19
|
const filters = (loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
|
|
33
20
|
|
|
@@ -42,7 +29,10 @@ export default async function filterAPI(req) {
|
|
|
42
29
|
logger.file('autoindex/error', { name: params?.table, error: err.toString(), stack: err.stack });
|
|
43
30
|
});
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
const loadTemplate = loadTable?.form ? await getTemplate('form', loadTable.form) : {};
|
|
33
|
+
const extraColumns = loadTemplate?.extra ? Object.keys(loadTemplate?.schema || {}).filter(key => !columns.find(col => col.name === key)) : [];
|
|
34
|
+
|
|
35
|
+
filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name, extra: extraColumns.includes(el.id || el.name) }));
|
|
46
36
|
|
|
47
37
|
await Promise.all(filters.filter((el) => el.data && el.id && el.type !== 'Autocomplete').map(async (el) => {
|
|
48
38
|
const cls = await getSelect(el.data, pg);
|
|
@@ -50,13 +40,14 @@ export default async function filterAPI(req) {
|
|
|
50
40
|
if (!cls || !loadTable.table) return;
|
|
51
41
|
const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
|
|
52
42
|
|
|
53
|
-
if (el.extra &&
|
|
54
|
-
const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id,
|
|
43
|
+
if (el.extra && Array.isArray(cls?.arr || cls)) {
|
|
44
|
+
const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id, loadTable.table]);
|
|
55
45
|
const options = countArr.rows.map(cel => {
|
|
56
|
-
const data = cls.find(c => c.id === cel.id);
|
|
46
|
+
const data = (cls?.arr || cls).find(c => c.id === cel.id);
|
|
57
47
|
return { ...cel, ...data };
|
|
58
48
|
});
|
|
59
49
|
Object.assign(el, { options });
|
|
50
|
+
return;
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
const q = pg.pgType[dataTypeID]?.includes('[]')
|
|
@@ -64,6 +55,7 @@ export default async function filterAPI(req) {
|
|
|
64
55
|
: `select ${el.id}::text as id,count(*) from (${optimizedSQL})q group by ${el.id} limit 100`;
|
|
65
56
|
|
|
66
57
|
const countArr = await pg.queryCache(q, { table: loadTable.table });
|
|
58
|
+
|
|
67
59
|
if (countArr.timeout) {
|
|
68
60
|
Object.assign(el, { timeout: countArr.timeout });
|
|
69
61
|
console.log('timeout filter', params.table, el.id);
|