@opengis/fastify-table 1.2.72 → 1.2.74

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.2.72",
3
+ "version": "1.2.74",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -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 extraKeys = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key));
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: extraKeys.map((el) => `,(select value_text as "${el.replace(/'/g, "''")}" from ${table} where property_key='${el.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
18
- extraKeys,
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, extraKeys } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
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
 
@@ -66,7 +66,7 @@ export default async function getFilterSQL({
66
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 = [] } = !extra ? await pg.query(fieldQuery) : { fields: extraKeys.map(el => ({ name: el.name, dataTypeID: 25 })) };
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 && extraKeys?.includes?.(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);
@@ -128,7 +128,7 @@ export default async function getFilterSQL({
128
128
 
129
129
  // table
130
130
 
131
- const obj = { table: modelQuery, orderby: body?.order || body?.orderby, sqlTable, sqlInline, extraSqlColumns, q };
131
+ const obj = { body, extraSqlColumns, table, q };
132
132
  const optimizedSQL = `select * from ${getOptimizedQuery(obj)} `;
133
133
  const tableCount = getOptimizedQuery(obj, true);
134
134
 
@@ -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,194 +1,110 @@
1
- const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
2
- const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer'];
1
+ import getRangeQuery from "./getRangeQuery.js";
3
2
 
4
- const isValidDate = (dateStr) => {
5
- if (!dateStr) return false;
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.pgType?.[dataTypeID] || pg.pgType?.[{ date: 1114 }[filterType] || 25];
10
+ const fieldType = pg?.pgType?.[dataTypeID] || pg?.pgType?.[{ date: 1114 }[filterType] || 25];
70
11
 
71
- if (!name || !value || !fieldType) return {};
72
-
73
- // Date filter
74
- if (filterType === 'date' /* && dateTypeList.includes(fieldType) */) {
75
- const [min, max] = getDates(value, name);
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
- // with sql subquery
83
- if (sql) {
84
- return { op: '=', query: sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`) };
85
- }
18
+ // my rows
19
+ if (value === 'me' && uid) {
20
+ return { op: '=', query: `${name}::text = '${uid}'` };
21
+ }
86
22
 
87
- // preset values
88
- if (['cw', 'cm', 'cq', 'cy'].includes(value)) {
89
- return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
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
- // preset values: current day
93
- if (value === 'cd') {
94
- return { op: '=', query: `${name}::date = '${min}'::date` };
29
+ // geometry
30
+ if (['geometry'].includes(fieldType)) {
31
+ if (matchNull) {
32
+ return { op: 'nullable', query: `${name} ${matchNull}` };
95
33
  }
96
34
 
97
- // extra columns, example: crm.extra_data
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
- // same date
106
- if (max && min && min === max) {
107
- return { op: 'strict', query: `${name}::date = '${min}'::date` };
37
+ if (bbox?.length !== 4) {
38
+ return {};
108
39
  }
109
40
 
110
- // default
111
- return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
41
+ const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
42
+ return { op: '&&', query };
112
43
  }
113
44
 
114
- // Range filter, example: 100-500
115
- if (filterType === 'range' /* && numberTypeList.includes(fieldType) */) {
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: sql.replace(/\$1/g, min).replace(/\$2/g, max) };
52
+ return { op: '=', query: querysql };
121
53
  }
122
54
 
123
- if (min === 'min' && max === 'max') {
124
- return { op: 'between', query: 'false' };
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
- // default
128
- const query = (max === 'max' ? `${name} > ${min}` : null) || (min === 'min' ? `${name} < ${max}` : null) || `${name} between ${min} and ${max}`;
129
- return { op: 'between', query };
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
- // Autocomplete, Check, Text filter with sql subquery
133
- if (sql) {
134
- // autocomplete, check
135
- if (sql?.indexOf('any($1)') !== -1) {
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 = sql.replace(/\$1/g, `'${value}'`);
142
- return { op: '=', query };
77
+ const query = `lower(${name}::text) ${op} lower('${value}')`;
78
+
79
+ return { op, query };
143
80
  }
144
81
 
145
- const matchNull = { null: 'is null', notnull: 'is not null' }[value];
146
- const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
147
- const matchMulti = value?.indexOf(',') !== -1 ? `= any('{${value}}'::text[])` : null;
148
- const match = matchNull || matchBoolean || matchMulti || (operator === '=' ? `='${value}'` : `ilike '%${value}%'`);
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
+ }
149
87
 
150
88
  /* select query - from admin.cls / filter options */
151
- if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
89
+ if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
90
+ // with sql subquery
91
+ if (sql) {
92
+ return { op: '=', query: querysql };
93
+ }
152
94
 
153
95
  // extra table, example: crm.extra_data
154
96
  if (extra?.table && pk) {
155
- const subquery = filterType === 'autocomplete' ? match : `in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}))`;
156
- const query = select
157
- ? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${subquery} )`
158
- : `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${match})`;
159
- return { op: operator, query };
160
- }
161
-
162
- // default
163
- const query = select && filterType === 'text'
164
- ? `${name}::text in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}) )` // filter with cls
165
- : `${name}::text ${match}`; // simple filter
166
- return { op: operator, query };
167
- }
168
-
169
- // my rows
170
- if (value === 'me' && uid) {
171
- if (sql) {
172
- 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 };
173
99
  }
174
100
 
175
- const query = extra?.table && pk && fieldType === 'text'
176
- ? `${pk} in (select ${pk} from ${extra.table} where uid = '${uid}')`
177
- : `${name}::text = '${uid}'`;
101
+ // nullable + default
102
+ const query = `${name} ${match}`;
178
103
  return { op: '=', query };
179
104
  }
180
105
 
181
- const formatType = {
182
- float8: 'numeric',
183
- int4: 'numeric',
184
- int8: 'numeric',
185
- varchar: 'text',
186
- bool: 'boolean',
187
- geometry: 'geom',
188
- }[fieldType] || 'text';
189
-
190
- // sql column?
191
- if (optimize && optimize.name !== optimize.pk) {
106
+ // sql column
107
+ if (optimize && optimize.name !== optimize.pk && false) {
192
108
  const val = filterType === 'text' ? `ilike '%${value}%'` : `= any('{${value}}')`;
193
109
  return {
194
110
  op: '~',
@@ -199,60 +115,5 @@ function formatValue({
199
115
  };
200
116
  }
201
117
 
202
- // filter text array column
203
- if (fieldType?.includes('[]')) {
204
- return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
205
- }
206
-
207
- // filter multiple values, example: 1,2,3
208
- if (value?.indexOf(',') !== -1) {
209
- const values = value.split(',').filter((el) => el !== 'null');
210
-
211
- // extra table, example: crm.extra_data
212
- if (extra?.table && pk) {
213
- const query = value?.indexOf('null') !== -1
214
- ? `${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(',')}) ) )`
215
- : `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
216
- return { op: 'in', query };
217
- }
218
-
219
- // default
220
- const query = value?.indexOf('null') !== -1
221
- ? `( ${name} is null or ${name}::text in (${values?.map((el) => `'${el}'`).join(',')}) )`
222
- : `${name}::text in (${value.split(',')?.map((el) => `'${el}'`).join(',')})`;
223
- return { op: 'in', query };
224
- }
225
-
226
- if (['<', '>'].includes(operator)) {
227
- const query = `${name} ${operator} '${value}'::${formatType}`;
228
- return { op: operator, query };
229
- }
230
-
231
- if (operator === '=' && filterType !== 'text' && !filter?.data) {
232
- const query = {
233
- null: `${name} is null`,
234
- notnull: `${name} is not null`,
235
- }[value] || `${name}::${formatType}='${value}'::${formatType}`;
236
- return { op: '=', query };
237
- }
238
-
239
- // json
240
- if (name.includes('.')) {
241
- const [col, prop] = name.split('.');
242
- const query = ` ${col}->>'${prop}' in ('${value.join("','")}')`;
243
- return { op: 'in', query };
244
- }
245
-
246
- // geometry
247
- if (['geometry'].includes(fieldType)) {
248
- const bbox = value[0].split('_');
249
-
250
- if (bbox?.length === 4) {
251
- const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
252
- return { op: '&&', query };
253
- }
254
- }
255
118
  return {};
256
119
  }
257
-
258
- 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) || fields?.find((el) => el.name === optimize?.pk) || {};
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
 
@@ -1,6 +1,10 @@
1
- export default function getOptimizedQuery({ table, orderby, sqlTable, sqlInline, extraSqlColumns, q }, count) {
2
- if (sqlInline) {
3
- return `(select * ${extraSqlColumns || ''} ${sqlInline ? sqlInline : ''} from ${table} t ${sqlTable && count ? ` ${sqlTable}` : ''})q where 1=1 and ${q ? q.replace('q.', 't.') : '1=1'} ${orderby ? `order by ${orderby}` : ''}`;
4
- }
5
- return `(select * ${extraSqlColumns || ''} ${sqlInline ? sqlInline : ''} from ${table} t ${sqlTable && count ? ` ${sqlTable}` : ''} where 1=1 and ${q ? q.replace('q.', 't.') : '1=1'} ${orderby ? `order by ${orderby}` : ''})q`;
1
+ export default function getOptimizedQuery({ body, extraSqlColumns, table, q }, count) {
2
+ const order = body?.orderby || body?.order ? `order by ${body?.orderby || body?.order}` : '';
3
+
4
+ const tableName = body?.table || body?.model || table;
5
+
6
+ const sqlList = body?.sql?.filter((el) => !el.disabled && el?.sql?.replace && (count ? el.count !== false : true))
7
+ .map((el) => ` left join lateral (${el.filter ? el.sql.replace(/limit 1/ig, '') : el.sql}) as ${el.name} on 1=1 `).join(' ');
8
+
9
+ return `(select * ${extraSqlColumns || ''} from ${tableName} t ${sqlList ? ` ${sqlList}` : ''} where 1=1 and ${q?.replace('q.', 't.') || '1=1'} ${order})q`;
6
10
  }
@@ -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, getFilterSQL, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
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, query = {}, pg = pgClients.client, user = {},
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
- filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name }));
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 && el.type === 'select' && Array.isArray(cls)) {
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, params.table]);
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);