@open-mercato/core 0.6.7-develop.6706.1.b3a4c759bb → 0.6.7-develop.6726.1.983ae8a07e
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/admin/nav.js +14 -4
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +8 -19
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +31 -14
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customers/api/utils.js +12 -18
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +17 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +7 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/cli.js +11 -4
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +4 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +14 -10
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js +4 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +20 -2
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/dashboards/lib/aggregations.js +76 -40
- package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
- package/dist/modules/dashboards/lib/exactDecimal.js +69 -0
- package/dist/modules/dashboards/lib/exactDecimal.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +228 -5
- package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js +3 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/staleTime.js +5 -0
- package/dist/modules/feature_toggles/components/hooks/staleTime.js.map +7 -0
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/route.js +16 -1
- package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js +32 -0
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js.map +7 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +0 -5
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js +14 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js.map +7 -0
- package/dist/modules/messages/lib/searchLookup.js +8 -16
- package/dist/modules/messages/lib/searchLookup.js.map +2 -2
- package/dist/modules/query_index/api/openapi.js +13 -1
- package/dist/modules/query_index/api/openapi.js.map +2 -2
- package/dist/modules/query_index/api/status.js +165 -105
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +128 -41
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +11 -4
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +12 -5
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +36 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +1 -1
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +7 -4
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +14 -3
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +22 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +2 -35
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +2 -2
- package/dist/modules/sales/data/validators.js +25 -4
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/admin/nav.ts +24 -4
- package/src/modules/auth/api/users/route.ts +8 -28
- package/src/modules/auth/lib/backendChrome.tsx +51 -13
- package/src/modules/customers/api/utils.ts +12 -31
- package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +35 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +10 -1
- package/src/modules/customers/cli.ts +11 -4
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +4 -0
- package/src/modules/customers/components/formConfig.tsx +12 -8
- package/src/modules/dashboards/api/widgets/data/batch/route.ts +4 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +21 -0
- package/src/modules/dashboards/lib/aggregations.ts +122 -43
- package/src/modules/dashboards/lib/exactDecimal.ts +96 -0
- package/src/modules/dashboards/services/widgetDataService.ts +342 -4
- package/src/modules/feature_toggles/components/FeatureGuard.tsx +9 -2
- package/src/modules/feature_toggles/components/hooks/staleTime.ts +5 -0
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.ts +13 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagJson.ts +12 -3
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts +10 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts +10 -1
- package/src/modules/inbox_ops/api/proposals/route.ts +20 -1
- package/src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts +39 -0
- package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +0 -5
- package/src/modules/integrations/widgets/injection/external-ids/widget.ts +13 -0
- package/src/modules/messages/lib/searchLookup.ts +11 -34
- package/src/modules/query_index/api/openapi.ts +12 -0
- package/src/modules/query_index/api/status.ts +238 -136
- package/src/modules/query_index/components/QueryIndexesTable.tsx +192 -46
- package/src/modules/query_index/i18n/de.json +5 -3
- package/src/modules/query_index/i18n/en.json +5 -3
- package/src/modules/query_index/i18n/es.json +5 -3
- package/src/modules/query_index/i18n/pl.json +5 -3
- package/src/modules/query_index/lib/batch.ts +11 -4
- package/src/modules/query_index/lib/document.ts +35 -4
- package/src/modules/query_index/lib/engine.ts +53 -5
- package/src/modules/query_index/lib/indexer.ts +1 -1
- package/src/modules/query_index/lib/search-tokens.ts +13 -4
- package/src/modules/sales/api/documents/factory.ts +28 -3
- package/src/modules/sales/commands/documents.ts +26 -2
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +2 -48
- package/src/modules/sales/data/validators.ts +50 -5
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSearchFieldBlocklisted,
|
|
3
|
+
resolveSearchConfig
|
|
4
|
+
} from "@open-mercato/shared/lib/search/config";
|
|
1
5
|
const AGGREGATE_SEARCH_FIELD = "search_text";
|
|
2
6
|
function normalizeScopeValue(value) {
|
|
3
7
|
if (value === void 0 || value === null || value === "") return null;
|
|
@@ -11,11 +15,12 @@ function normalizeValue(value) {
|
|
|
11
15
|
if (value === void 0) return null;
|
|
12
16
|
return value;
|
|
13
17
|
}
|
|
14
|
-
function collectAggregateSearchValues(field, value) {
|
|
18
|
+
function collectAggregateSearchValues(field, value, entityType, config) {
|
|
15
19
|
const lower = field.toLowerCase();
|
|
16
20
|
if (lower === AGGREGATE_SEARCH_FIELD || lower === "id" || lower.endsWith("_id") || lower.endsWith(".id") || lower.endsWith("_at") || ["created_at", "updated_at", "deleted_at", "tenant_id", "organization_id"].includes(lower)) {
|
|
17
21
|
return [];
|
|
18
22
|
}
|
|
23
|
+
if (isSearchFieldBlocklisted(field, entityType, config)) return [];
|
|
19
24
|
if (typeof value === "string") {
|
|
20
25
|
const trimmed = value.trim();
|
|
21
26
|
return trimmed.length > 0 ? [trimmed] : [];
|
|
@@ -25,11 +30,13 @@ function collectAggregateSearchValues(field, value) {
|
|
|
25
30
|
}
|
|
26
31
|
return [];
|
|
27
32
|
}
|
|
28
|
-
function attachAggregateSearchField(doc) {
|
|
33
|
+
function attachAggregateSearchField(doc, options = {}) {
|
|
34
|
+
const config = options.config ?? resolveSearchConfig();
|
|
35
|
+
const entityType = options.entityType ?? null;
|
|
29
36
|
const parts = [];
|
|
30
37
|
const seen = /* @__PURE__ */ new Set();
|
|
31
38
|
for (const [field, value] of Object.entries(doc)) {
|
|
32
|
-
const values = collectAggregateSearchValues(field, value);
|
|
39
|
+
const values = collectAggregateSearchValues(field, value, entityType, config);
|
|
33
40
|
for (const entry of values) {
|
|
34
41
|
const key = entry.toLowerCase();
|
|
35
42
|
if (seen.has(key)) continue;
|
|
@@ -42,7 +49,7 @@ function attachAggregateSearchField(doc) {
|
|
|
42
49
|
}
|
|
43
50
|
return doc;
|
|
44
51
|
}
|
|
45
|
-
function buildIndexDocument(baseRow, customFieldValues = [], scope = {}) {
|
|
52
|
+
function buildIndexDocument(baseRow, customFieldValues = [], scope = {}, options = {}) {
|
|
46
53
|
const doc = {};
|
|
47
54
|
for (const [key, value] of Object.entries(baseRow)) {
|
|
48
55
|
doc[key] = value;
|
|
@@ -75,7 +82,7 @@ function buildIndexDocument(baseRow, customFieldValues = [], scope = {}) {
|
|
|
75
82
|
doc[key] = values;
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
|
-
return attachAggregateSearchField(doc);
|
|
85
|
+
return attachAggregateSearchField(doc, options);
|
|
79
86
|
}
|
|
80
87
|
export {
|
|
81
88
|
AGGREGATE_SEARCH_FIELD,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/query_index/lib/document.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import {\n isSearchFieldBlocklisted,\n resolveSearchConfig,\n type SearchConfig,\n} from '@open-mercato/shared/lib/search/config'\n\nexport type IndexDocumentScope = {\n organizationId?: string | null\n tenantId?: string | null\n}\n\nexport type IndexCustomFieldValue = {\n key: string\n value: unknown\n organizationId?: string | null\n tenantId?: string | null\n}\n\nexport const AGGREGATE_SEARCH_FIELD = 'search_text'\n\n/**\n * Controls which fields may contribute text to the `search_text` aggregate.\n *\n * Both properties are optional so existing callers keep working unchanged: an\n * omitted `config` is resolved from the environment, and an omitted `entityType`\n * means only the global blocklist entries apply.\n */\nexport type AggregateSearchOptions = {\n entityType?: string | null\n config?: SearchConfig\n}\n\nfunction normalizeScopeValue(value: string | null | undefined): string | null {\n if (value === undefined || value === null || value === '') return null\n return value\n}\n\nfunction isScopedValueVisible(\n scopeValue: string | null,\n fieldValue: string | null,\n): boolean {\n if (scopeValue === null) return fieldValue === null\n return fieldValue === null || fieldValue === scopeValue\n}\n\nfunction normalizeValue(value: unknown): unknown {\n if (value === undefined) return null\n return value\n}\n\nfunction collectAggregateSearchValues(\n field: string,\n value: unknown,\n entityType: string | null,\n config: SearchConfig,\n): string[] {\n const lower = field.toLowerCase()\n if (\n lower === AGGREGATE_SEARCH_FIELD\n || lower === 'id'\n || lower.endsWith('_id')\n || lower.endsWith('.id')\n || lower.endsWith('_at')\n || ['created_at', 'updated_at', 'deleted_at', 'tenant_id', 'organization_id'].includes(lower)\n ) {\n return []\n }\n\n if (isSearchFieldBlocklisted(field, entityType, config)) return []\n\n if (typeof value === 'string') {\n const trimmed = value.trim()\n return trimmed.length > 0 ? [trimmed] : []\n }\n\n if (Array.isArray(value)) {\n return value\n .filter((entry): entry is string => typeof entry === 'string')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n }\n\n return []\n}\n\nexport function attachAggregateSearchField(\n doc: Record<string, unknown>,\n options: AggregateSearchOptions = {},\n): Record<string, unknown> {\n const config = options.config ?? resolveSearchConfig()\n const entityType = options.entityType ?? null\n const parts: string[] = []\n const seen = new Set<string>()\n\n for (const [field, value] of Object.entries(doc)) {\n const values = collectAggregateSearchValues(field, value, entityType, config)\n for (const entry of values) {\n const key = entry.toLowerCase()\n if (seen.has(key)) continue\n seen.add(key)\n parts.push(entry)\n }\n }\n\n if (parts.length > 0) {\n doc[AGGREGATE_SEARCH_FIELD] = parts.join('\\n')\n }\n\n return doc\n}\n\nexport function buildIndexDocument(\n baseRow: Record<string, unknown>,\n customFieldValues: Iterable<IndexCustomFieldValue> = [],\n scope: IndexDocumentScope = {},\n options: AggregateSearchOptions = {},\n): Record<string, unknown> {\n const doc: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(baseRow)) {\n doc[key] = value\n }\n\n const scopeOrg = normalizeScopeValue(scope.organizationId ?? null)\n const scopeTenant = normalizeScopeValue(scope.tenantId ?? null)\n\n const grouped = new Map<string, unknown[]>()\n for (const field of customFieldValues) {\n const org = normalizeScopeValue(field.organizationId ?? null)\n const tenant = normalizeScopeValue(field.tenantId ?? null)\n\n if (!isScopedValueVisible(scopeOrg, org)) continue\n if (!isScopedValueVisible(scopeTenant, tenant)) continue\n\n const bucketKey = `cf:${field.key}`\n let bucket = grouped.get(bucketKey)\n if (!bucket) {\n bucket = []\n grouped.set(bucketKey, bucket)\n }\n\n const { value } = field\n if (Array.isArray(value)) {\n for (const entry of value) bucket.push(normalizeValue(entry))\n } else {\n bucket.push(normalizeValue(value))\n }\n }\n\n for (const [key, values] of grouped.entries()) {\n if (values.length === 1) {\n doc[key] = values[0]\n } else if (values.length > 1) {\n doc[key] = values\n }\n }\n\n return attachAggregateSearchField(doc, options)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
|
|
16
16
|
import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
|
|
17
17
|
import { runBeforeQueryPipeline, runAfterQueryPipeline } from "@open-mercato/shared/lib/query/query-extension-runner";
|
|
18
|
+
import { warnOnCiphertextLikeFallback } from "@open-mercato/shared/lib/query/ciphertext-search-warning";
|
|
18
19
|
import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
|
|
19
20
|
import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
|
|
20
21
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
@@ -316,10 +317,10 @@ class HybridQueryEngine {
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
const searchSources = indexSources.map((src) => ({ entity: String(src.entityId), recordIdColumn: src.recordIdColumn })).filter((src) => src.recordIdColumn && src.entity);
|
|
319
|
-
const
|
|
320
|
+
const searchFilters = normalizedFilters.filter((filter) => filter.op === "like" || filter.op === "ilike");
|
|
321
|
+
const hasSearchTokens = searchEnabled && searchSources.length && searchFilters.length ? await this.searchSourcesHaveTokens(searchSources, opts.tenantId ?? null, orgScope) : false;
|
|
320
322
|
const searchRuntime = { ...searchRuntimeBase, searchSources, enabled: searchEnabled && hasSearchTokens };
|
|
321
323
|
const joinSearchAvailability = /* @__PURE__ */ new Map();
|
|
322
|
-
const searchFilters = normalizeFilters(opts.filters).filter((filter) => filter.op === "like" || filter.op === "ilike");
|
|
323
324
|
if (searchFilters.length) {
|
|
324
325
|
this.logSearchDebug("search:init", {
|
|
325
326
|
entity,
|
|
@@ -346,6 +347,36 @@ class HybridQueryEngine {
|
|
|
346
347
|
organizationScope: orgScope,
|
|
347
348
|
searchSources
|
|
348
349
|
});
|
|
350
|
+
const baseSearchFilters = [...baseFilters, ...cfFilters].filter((filter) => filter.op === "like" || filter.op === "ilike");
|
|
351
|
+
const fallbackFields = baseSearchFilters.filter((filter) => !searchRuntime.enabled || typeof filter.value !== "string" || tokenizeText(filter.value, searchConfig).hashes.length === 0).map((filter) => String(filter.field));
|
|
352
|
+
if (fallbackFields.length) {
|
|
353
|
+
await warnOnCiphertextLikeFallback({
|
|
354
|
+
entity: String(entity),
|
|
355
|
+
fields: fallbackFields,
|
|
356
|
+
tenantId: opts.tenantId ?? null,
|
|
357
|
+
// `searchEnabled` also folds in the missing-table case, which is
|
|
358
|
+
// "no usable tokens" rather than "the operator switched search off".
|
|
359
|
+
reason: searchRuntime.enabled ? "no-indexable-tokens" : searchConfig.enabled ? "no-search-tokens" : "search-disabled",
|
|
360
|
+
service: this.getEncryptionService()
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const [alias, joinedFilters] of joinFilters) {
|
|
365
|
+
const filters = joinedFilters.filter((entry) => entry.op === "like" || entry.op === "ilike");
|
|
366
|
+
if (!filters.length) continue;
|
|
367
|
+
const join = joinMap.get(alias);
|
|
368
|
+
if (!join?.entityId) continue;
|
|
369
|
+
const hasJoinedTokens = searchEnabled ? await this.hasSearchTokens(String(join.entityId), opts.tenantId ?? null, orgScope) : false;
|
|
370
|
+
joinSearchAvailability.set(String(join.entityId), hasJoinedTokens);
|
|
371
|
+
const fallbackFields = filters.filter((filter) => !hasJoinedTokens || typeof filter.value !== "string" || tokenizeText(filter.value, searchConfig).hashes.length === 0).map((filter) => filter.column);
|
|
372
|
+
if (!fallbackFields.length) continue;
|
|
373
|
+
await warnOnCiphertextLikeFallback({
|
|
374
|
+
entity: String(join.entityId),
|
|
375
|
+
fields: fallbackFields,
|
|
376
|
+
tenantId: opts.tenantId ?? null,
|
|
377
|
+
reason: hasJoinedTokens ? "no-indexable-tokens" : searchConfig.enabled ? "no-search-tokens" : "search-disabled",
|
|
378
|
+
service: this.getEncryptionService()
|
|
379
|
+
});
|
|
349
380
|
}
|
|
350
381
|
const hasNonBaseSearchSource = searchSources.some(
|
|
351
382
|
(src) => src.entity !== String(entity) || src.recordIdColumn !== "b.id"
|
|
@@ -1356,9 +1387,11 @@ class HybridQueryEngine {
|
|
|
1356
1387
|
const alias = "ce";
|
|
1357
1388
|
const orgScope = this.resolveOrganizationScope(opts);
|
|
1358
1389
|
if (!opts.tenantId) throw new Error("QueryEngine: tenantId is required");
|
|
1390
|
+
const normalizedFilters = normalizeFilters(opts.filters);
|
|
1391
|
+
const hasSearchFilter = normalizedFilters.some((filter) => filter.op === "like" || filter.op === "ilike");
|
|
1359
1392
|
const searchConfig = resolveSearchConfig();
|
|
1360
1393
|
const searchEnabled = searchConfig.enabled && await this.tableExists("search_tokens");
|
|
1361
|
-
const hasSearchTokens = searchEnabled ? await this.hasSearchTokens(entity, opts.tenantId ?? null, orgScope) : false;
|
|
1394
|
+
const hasSearchTokens = searchEnabled && hasSearchFilter ? await this.hasSearchTokens(entity, opts.tenantId ?? null, orgScope) : false;
|
|
1362
1395
|
const searchRuntime = {
|
|
1363
1396
|
enabled: searchEnabled && hasSearchTokens,
|
|
1364
1397
|
config: searchConfig,
|
|
@@ -1366,7 +1399,6 @@ class HybridQueryEngine {
|
|
|
1366
1399
|
tenantId: opts.tenantId ?? null,
|
|
1367
1400
|
mintAlias: createSearchAliasMinter()
|
|
1368
1401
|
};
|
|
1369
|
-
const normalizedFilters = normalizeFilters(opts.filters);
|
|
1370
1402
|
const applyScope = (q) => {
|
|
1371
1403
|
let next = q.where(`${alias}.entity_type`, "=", entity).where(`${alias}.tenant_id`, "=", opts.tenantId);
|
|
1372
1404
|
if (orgScope) {
|