@cobre-npm/library-portal-core 0.52.0 → 0.54.0
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/README.md +26 -8
- package/dist/cjs/exports/index.d.ts +7 -2
- package/dist/cjs/exports/index.js +8 -3
- package/dist/cjs/exports/interfaces/translation.interface.d.ts +11 -0
- package/dist/cjs/exports/interfaces/translation.interface.js +2 -0
- package/dist/cjs/exports/utils/search-filter.utils.d.ts +19 -0
- package/dist/cjs/exports/utils/search-filter.utils.js +68 -0
- package/dist/cjs/movements/types/catalog.d.ts +10 -0
- package/dist/cjs/movements/types/catalog.js +2 -0
- package/dist/cjs/movements/types/locales/en.json +3 -1
- package/dist/cjs/movements/types/locales/es-mex.json +3 -1
- package/dist/cjs/movements/types/utils/movement-type.utils.d.ts +1 -1
- package/dist/cjs/movements/types/utils/movement-type.utils.js +1 -1
- package/dist/cjs/transactions/types/catalog.d.ts +24 -0
- package/dist/cjs/transactions/types/catalog.js +24 -0
- package/dist/cjs/transactions/types/locales/en.json +5 -1
- package/dist/cjs/transactions/types/locales/es-mex.json +5 -1
- package/dist/esm/exports/index.js +8 -3
- package/dist/esm/exports/interfaces/translation.interface.js +1 -0
- package/dist/esm/exports/utils/search-filter.utils.js +64 -0
- package/dist/esm/movements/types/catalog.js +2 -0
- package/dist/esm/movements/types/locales/en.json +3 -1
- package/dist/esm/movements/types/locales/es-mex.json +3 -1
- package/dist/esm/movements/types/utils/movement-type.utils.js +1 -1
- package/dist/esm/transactions/types/catalog.js +24 -0
- package/dist/esm/transactions/types/locales/en.json +5 -1
- package/dist/esm/transactions/types/locales/es-mex.json +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -272,7 +272,7 @@ queryMovementStatuses({ lang: "en", type: "error" }) // error, failed, rejected
|
|
|
272
272
|
`queryTransactionTypes`/`queryDocumentTypes`, no default field. `code`, `line`, `category`, `group` and
|
|
273
273
|
`groupLabel` are filterable (equality only; a list matches any of its values). Filter by `group`, not
|
|
274
274
|
`groupLabel` — the label is display text. Both are optional on the record: **a key that is present always filters**, so
|
|
275
|
-
`groupLabel: undefined` matches the
|
|
275
|
+
`groupLabel: undefined` matches the rails that don't carry a group, not "no filter". An optional UI
|
|
276
276
|
filter has to be spread in conditionally:
|
|
277
277
|
|
|
278
278
|
```ts
|
|
@@ -431,7 +431,7 @@ queryTransactionTypes({ lang: "en", category: "top_up" }) // col_top_up_credit
|
|
|
431
431
|
listed under [`movements`](#movements)). Labels are usually "what you did · rail" (`Outgoing transfer ·
|
|
432
432
|
SPEI`). Types that stand for a collapsed matrix row (`cbmm_*`, `internal_*`, `mex_*`, `col_debit`/`col_credit`, `misc_*`,
|
|
433
433
|
`adjustment_*`, `col_cb_*`, the corridors, ramps and `stable_payout_*`, plus `transfer_credit`) add a
|
|
434
|
-
"· débito" / "· crédito" (en "· debit" / "· credit") suffix; elsewhere the direction is in the verb
|
|
434
|
+
"· débito" / "· crédito" (en "· debit" / "· credit") suffix, except `offramp_treasury_credit`, whose label has none; elsewhere the direction is in the verb
|
|
435
435
|
("Outgoing transfer", "Incoming transfer"). `col_cb_*` keeps "Cobre Balance" in its label. `misc_*`,
|
|
436
436
|
`internal_*` and `adjustment_*` have `line: "unclassified"`, and `misc_*` also `category: "unclassified"`.
|
|
437
437
|
- **types query** (i18n `queryX()`): `queryTransactionTypes({ lang, ...filters })` — `lang` is **required inside
|
|
@@ -485,8 +485,9 @@ import type {
|
|
|
485
485
|
|
|
486
486
|
### `exports`
|
|
487
487
|
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
The export engine's (`util-platform-exports`) request/response contract, verified against its real DTO and
|
|
489
|
+
entity, not against what any consumer had guessed, plus its operator constants and a pure search → export filter
|
|
490
|
+
translator:
|
|
490
491
|
|
|
491
492
|
```ts
|
|
492
493
|
import {
|
|
@@ -494,6 +495,7 @@ import {
|
|
|
494
495
|
type ExportResponse, type ExportStatus, type ExportCreator, type ExportStatusState,
|
|
495
496
|
EXPORT_FORMATS, type ExportFormat,
|
|
496
497
|
EXPORT_QUERY_OPERATORS, type ExportQueryOperator,
|
|
498
|
+
toExportFilterConditions, type ExportFilterTranslation, type ExportFilterTranslationOptions,
|
|
497
499
|
} from "@cobre-npm/library-portal-core/exports"
|
|
498
500
|
```
|
|
499
501
|
|
|
@@ -502,10 +504,26 @@ import {
|
|
|
502
504
|
`status`/`updated_at`/`export_path` in place as it processes. Both the exports list/status view in
|
|
503
505
|
`portal` and any widget that creates exports read/write this one shape; keeping two hand-typed copies
|
|
504
506
|
is what let each one drift from the real contract in its own way (see the two fixes below).
|
|
505
|
-
- `EXPORT_QUERY_OPERATORS` is the engine's **real** allow-list (`eq`, `neq`, `gt`, `lt`, `lte`, `gte`
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
507
|
+
- `EXPORT_QUERY_OPERATORS` is the engine's **real** allow-list (`eq`, `neq`, `gt`, `lt`, `lte`, `gte`, `in`, `like`,
|
|
508
|
+
`between`); `in` and `between` take comma-separated values. A layout's catalog declares which ones each column
|
|
509
|
+
accepts, so a consumer validating a filter before sending it needs the intersection of both.
|
|
510
|
+
- `toExportFilterConditions(filter, { fieldMap })` translates the `SearchFilter` a table sends to the search API into
|
|
511
|
+
`ExportFilterCondition[]`, so the file holds exactly the rows the table shows. `eq` stays, `not_eq` → `neq`,
|
|
512
|
+
`contains` → `like` (both case-insensitive substring matches), `gte`/`lte` stay, and an OR of `eq` on one field (a
|
|
513
|
+
multi-select filter) → `in`. Date conditions are translated like any other; checking them against the export's
|
|
514
|
+
31-day limit is the caller's job. What has no export equivalent — `exists`, a blank `contains`, an OR across
|
|
515
|
+
fields or operators (a table's text search), an `in` value holding a comma, an empty OR (which matches nothing in
|
|
516
|
+
the search) — comes back in `untranslatable` instead of being dropped, so the caller can block the export rather
|
|
517
|
+
than ship a wider file. `fieldMap` renames only the fields whose layout column differs:
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
// filter: the same SearchFilter the view sends to the search API
|
|
521
|
+
toExportFilterConditions(filter, { fieldMap: { currency: "currency_pair" } })
|
|
522
|
+
// { conditions: [
|
|
523
|
+
// { column: "currency_pair", operator: "in", value: "usd/cop,usd/mxn" },
|
|
524
|
+
// { column: "created_at", operator: "gte", value: "2026-10-01T00:00:00" }
|
|
525
|
+
// ], untranslatable: [] }
|
|
526
|
+
```
|
|
509
527
|
- `ExportFilterCondition` is an `ExportQueryFilter` before its `type` is resolved
|
|
510
528
|
(`{ column: "state", operator: "neq", value: "pending_funds" }`). The type belongs to the layout's catalog,
|
|
511
529
|
so whoever reads the catalog fills it in and the column's type keeps one source of truth.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from "./interfaces/common.interface";
|
|
2
|
+
export * from "./interfaces/translation.interface";
|
|
2
3
|
export * from "./interfaces/request.interface";
|
|
3
4
|
export * from "./interfaces/response.interface";
|
|
5
|
+
export * from "./utils/search-filter.utils";
|
|
4
6
|
export declare const EXPORT_FORMATS: {
|
|
5
7
|
readonly CSV: "csv";
|
|
6
8
|
readonly JSON: "json";
|
|
@@ -8,8 +10,8 @@ export declare const EXPORT_FORMATS: {
|
|
|
8
10
|
export type ExportFormat = typeof EXPORT_FORMATS[keyof typeof EXPORT_FORMATS];
|
|
9
11
|
/**
|
|
10
12
|
* The export engine's real allow-list (verified against util-platform-exports'
|
|
11
|
-
* QueryOperator enum). A layout's catalog
|
|
12
|
-
*
|
|
13
|
+
* QueryOperator enum). `in` and `between` take comma-separated values. A layout's catalog declares which of
|
|
14
|
+
* them each column accepts, so a consumer validating a filter needs both lists.
|
|
13
15
|
*/
|
|
14
16
|
export declare const EXPORT_QUERY_OPERATORS: {
|
|
15
17
|
readonly EQ: "eq";
|
|
@@ -18,5 +20,8 @@ export declare const EXPORT_QUERY_OPERATORS: {
|
|
|
18
20
|
readonly LT: "lt";
|
|
19
21
|
readonly LTE: "lte";
|
|
20
22
|
readonly GTE: "gte";
|
|
23
|
+
readonly IN: "in";
|
|
24
|
+
readonly LIKE: "like";
|
|
25
|
+
readonly BETWEEN: "between";
|
|
21
26
|
};
|
|
22
27
|
export type ExportQueryOperator = typeof EXPORT_QUERY_OPERATORS[keyof typeof EXPORT_QUERY_OPERATORS];
|
|
@@ -16,16 +16,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.EXPORT_QUERY_OPERATORS = exports.EXPORT_FORMATS = void 0;
|
|
18
18
|
__exportStar(require("./interfaces/common.interface"), exports);
|
|
19
|
+
__exportStar(require("./interfaces/translation.interface"), exports);
|
|
19
20
|
__exportStar(require("./interfaces/request.interface"), exports);
|
|
20
21
|
__exportStar(require("./interfaces/response.interface"), exports);
|
|
22
|
+
__exportStar(require("./utils/search-filter.utils"), exports);
|
|
21
23
|
exports.EXPORT_FORMATS = {
|
|
22
24
|
CSV: "csv",
|
|
23
25
|
JSON: "json"
|
|
24
26
|
};
|
|
25
27
|
/**
|
|
26
28
|
* The export engine's real allow-list (verified against util-platform-exports'
|
|
27
|
-
* QueryOperator enum). A layout's catalog
|
|
28
|
-
*
|
|
29
|
+
* QueryOperator enum). `in` and `between` take comma-separated values. A layout's catalog declares which of
|
|
30
|
+
* them each column accepts, so a consumer validating a filter needs both lists.
|
|
29
31
|
*/
|
|
30
32
|
exports.EXPORT_QUERY_OPERATORS = {
|
|
31
33
|
EQ: "eq",
|
|
@@ -33,5 +35,8 @@ exports.EXPORT_QUERY_OPERATORS = {
|
|
|
33
35
|
GT: "gt",
|
|
34
36
|
LT: "lt",
|
|
35
37
|
LTE: "lte",
|
|
36
|
-
GTE: "gte"
|
|
38
|
+
GTE: "gte",
|
|
39
|
+
IN: "in",
|
|
40
|
+
LIKE: "like",
|
|
41
|
+
BETWEEN: "between"
|
|
37
42
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SearchCondition, SearchOrFilter } from "../../search/interfaces/filters.interface";
|
|
2
|
+
import type { ExportFilterCondition } from "./common.interface";
|
|
3
|
+
export interface ExportFilterTranslationOptions {
|
|
4
|
+
/** Search field → layout column, only where the two names differ (e.g. `{ currency: "currency_pair" }`). */
|
|
5
|
+
fieldMap?: Readonly<Record<string, string>>;
|
|
6
|
+
}
|
|
7
|
+
export interface ExportFilterTranslation {
|
|
8
|
+
conditions: ExportFilterCondition[];
|
|
9
|
+
/** Parts of the search filter with no export equivalent, so the caller can block or warn instead of dropping them. */
|
|
10
|
+
untranslatable: Array<SearchCondition | SearchOrFilter>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SearchFilter } from "../../search/interfaces/filters.interface";
|
|
2
|
+
import type { ExportFilterTranslationOptions, ExportFilterTranslation } from "../interfaces/translation.interface";
|
|
3
|
+
/**
|
|
4
|
+
* Translates the filter a table sends to the search API into the conditions the exports API takes, so an
|
|
5
|
+
* export can match what the table shows. What has no export equivalent comes back in `untranslatable`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* toExportFilterConditions({
|
|
9
|
+
* and: [
|
|
10
|
+
* { or: [{ field: "state", operator: "eq", value: "completed" }, { field: "state", operator: "eq", value: "failed" }] },
|
|
11
|
+
* { field: "created_at", operator: "gte", value: "2026-10-01T00:00:00" },
|
|
12
|
+
* ],
|
|
13
|
+
* })
|
|
14
|
+
* // { conditions: [
|
|
15
|
+
* // { column: "state", operator: "in", value: "completed,failed" },
|
|
16
|
+
* // { column: "created_at", operator: "gte", value: "2026-10-01T00:00:00" }
|
|
17
|
+
* // ], untranslatable: [] }
|
|
18
|
+
*/
|
|
19
|
+
export declare const toExportFilterConditions: (filter: SearchFilter, options?: ExportFilterTranslationOptions) => ExportFilterTranslation;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toExportFilterConditions = void 0;
|
|
4
|
+
/* Literals, not EXPORT_QUERY_OPERATORS: a value import from the barrel that re-exports this file is a runtime cycle. */
|
|
5
|
+
const SEARCH_TO_EXPORT_OPERATORS = {
|
|
6
|
+
eq: "eq",
|
|
7
|
+
not_eq: "neq",
|
|
8
|
+
contains: "like",
|
|
9
|
+
gte: "gte",
|
|
10
|
+
lte: "lte",
|
|
11
|
+
};
|
|
12
|
+
/* The export splits an `in` value on commas, so a value that holds one can't travel in it. */
|
|
13
|
+
const IN_SEPARATOR = ",";
|
|
14
|
+
const isAndFilter = (filter) => "and" in filter;
|
|
15
|
+
const isOrFilter = (part) => "or" in part;
|
|
16
|
+
const toColumn = (field, { fieldMap = {} }) => fieldMap[field] ?? field;
|
|
17
|
+
const toCondition = ({ field, operator, value }, options) => {
|
|
18
|
+
const exportOperator = SEARCH_TO_EXPORT_OPERATORS[operator];
|
|
19
|
+
const exportValue = String(value);
|
|
20
|
+
/* A blank contains matches every row with a value in the search; the export rejects a blank like and has no exists. */
|
|
21
|
+
if (!exportOperator || (exportOperator === "like" && exportValue.trim() === ""))
|
|
22
|
+
return null;
|
|
23
|
+
return { column: toColumn(field, options), operator: exportOperator, value: exportValue };
|
|
24
|
+
};
|
|
25
|
+
/* A multi-select filter reaches the search as an OR of eq on one field; the export reads it as in. */
|
|
26
|
+
const toInCondition = ({ or }, options) => {
|
|
27
|
+
const field = or[0]?.field;
|
|
28
|
+
const values = or.map(({ value }) => String(value));
|
|
29
|
+
const isSingleFieldEq = field !== undefined && or.every(part => part.operator === "eq" && part.field === field);
|
|
30
|
+
if (!isSingleFieldEq || values.some(value => value.includes(IN_SEPARATOR)))
|
|
31
|
+
return null;
|
|
32
|
+
return { column: toColumn(field, options), operator: "in", value: values.join(IN_SEPARATOR) };
|
|
33
|
+
};
|
|
34
|
+
const toPartCondition = (part, options) => {
|
|
35
|
+
if (!isOrFilter(part))
|
|
36
|
+
return toCondition(part, options);
|
|
37
|
+
const [singleCondition] = part.or;
|
|
38
|
+
return part.or.length === 1 && singleCondition ? toCondition(singleCondition, options) : toInCondition(part, options);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Translates the filter a table sends to the search API into the conditions the exports API takes, so an
|
|
42
|
+
* export can match what the table shows. What has no export equivalent comes back in `untranslatable`.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* toExportFilterConditions({
|
|
46
|
+
* and: [
|
|
47
|
+
* { or: [{ field: "state", operator: "eq", value: "completed" }, { field: "state", operator: "eq", value: "failed" }] },
|
|
48
|
+
* { field: "created_at", operator: "gte", value: "2026-10-01T00:00:00" },
|
|
49
|
+
* ],
|
|
50
|
+
* })
|
|
51
|
+
* // { conditions: [
|
|
52
|
+
* // { column: "state", operator: "in", value: "completed,failed" },
|
|
53
|
+
* // { column: "created_at", operator: "gte", value: "2026-10-01T00:00:00" }
|
|
54
|
+
* // ], untranslatable: [] }
|
|
55
|
+
*/
|
|
56
|
+
const toExportFilterConditions = (filter, options = {}) => {
|
|
57
|
+
const parts = isAndFilter(filter) ? filter.and : [filter];
|
|
58
|
+
const translation = { conditions: [], untranslatable: [] };
|
|
59
|
+
parts.forEach(part => {
|
|
60
|
+
const condition = toPartCondition(part, options);
|
|
61
|
+
if (condition)
|
|
62
|
+
translation.conditions.push(condition);
|
|
63
|
+
else
|
|
64
|
+
translation.untranslatable.push(part);
|
|
65
|
+
});
|
|
66
|
+
return translation;
|
|
67
|
+
};
|
|
68
|
+
exports.toExportFilterConditions = toExportFilterConditions;
|
|
@@ -131,6 +131,16 @@ export declare const MOVEMENT_TYPES: {
|
|
|
131
131
|
readonly line: "payout";
|
|
132
132
|
readonly category: "payments";
|
|
133
133
|
};
|
|
134
|
+
readonly swift: {
|
|
135
|
+
readonly code: "swift";
|
|
136
|
+
readonly line: "payout";
|
|
137
|
+
readonly category: "payments";
|
|
138
|
+
};
|
|
139
|
+
readonly sepa: {
|
|
140
|
+
readonly code: "sepa";
|
|
141
|
+
readonly line: "payout";
|
|
142
|
+
readonly category: "payments";
|
|
143
|
+
};
|
|
134
144
|
};
|
|
135
145
|
export type MovementType = keyof typeof MOVEMENT_TYPES;
|
|
136
146
|
export type MovementTypeRecord = (typeof MOVEMENT_TYPES)[MovementType] & {
|
|
@@ -26,4 +26,6 @@ exports.MOVEMENT_TYPES = {
|
|
|
26
26
|
stable_payout: { code: "stable_payout", line: "stable", category: "conversions", group: "stablecoin", groupLabel: "Stablecoin" },
|
|
27
27
|
breb: { code: "breb", line: "payout", category: "payments" },
|
|
28
28
|
global_pay_to_generic: { code: "global_pay_to_generic", line: "payout", category: "payments" },
|
|
29
|
+
swift: { code: "swift", line: "payout", category: "payments" },
|
|
30
|
+
sepa: { code: "sepa", line: "payout", category: "payments" },
|
|
29
31
|
};
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"cross_border": "International",
|
|
24
24
|
"stable_payout": "Stablecoin payment · Direct",
|
|
25
25
|
"breb": "Instant payment · Bre-B",
|
|
26
|
-
"global_pay_to_generic": "International payment"
|
|
26
|
+
"global_pay_to_generic": "International payment",
|
|
27
|
+
"swift": "International transfer · Swift",
|
|
28
|
+
"sepa": "International transfer · SEPA"
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"cross_border": "Internacional",
|
|
24
24
|
"stable_payout": "Pago en stablecoin · Directo",
|
|
25
25
|
"breb": "Pago instantáneo · Bre-B",
|
|
26
|
-
"global_pay_to_generic": "Pago internacional"
|
|
26
|
+
"global_pay_to_generic": "Pago internacional",
|
|
27
|
+
"swift": "Transferencia internacional · Swift",
|
|
28
|
+
"sepa": "Transferencia internacional · SEPA"
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -14,7 +14,7 @@ export type MovementTypeQuery = CatalogQuery<MovementTypeRecord> & {
|
|
|
14
14
|
* Filter by `group`, not `groupLabel` — the label is display text, not a stable filter key.
|
|
15
15
|
*
|
|
16
16
|
* `group` is optional on the record, so `group: undefined` is a present key like any other — it
|
|
17
|
-
* matches the
|
|
17
|
+
* matches the records that don't carry a group, not "no filter". A UI filter fed from an optional
|
|
18
18
|
* ref has to be spread in conditionally: `{ lang, ...(group ? { group } : {}) }`.
|
|
19
19
|
*/
|
|
20
20
|
export declare const queryMovementTypes: (query: MovementTypeQuery) => MovementTypeOption[];
|
|
@@ -23,7 +23,7 @@ exports.getMovementTypes = getMovementTypes;
|
|
|
23
23
|
* Filter by `group`, not `groupLabel` — the label is display text, not a stable filter key.
|
|
24
24
|
*
|
|
25
25
|
* `group` is optional on the record, so `group: undefined` is a present key like any other — it
|
|
26
|
-
* matches the
|
|
26
|
+
* matches the records that don't carry a group, not "no filter". A UI filter fed from an optional
|
|
27
27
|
* ref has to be spread in conditionally: `{ lang, ...(group ? { group } : {}) }`.
|
|
28
28
|
*/
|
|
29
29
|
const queryMovementTypes = (query) => {
|
|
@@ -263,6 +263,30 @@ export declare const TRANSACTION_TYPES: {
|
|
|
263
263
|
readonly line: "cbmm";
|
|
264
264
|
readonly category: "collections";
|
|
265
265
|
};
|
|
266
|
+
readonly global_rejected: {
|
|
267
|
+
readonly creditDebitType: "credit";
|
|
268
|
+
readonly code: "global_rejected";
|
|
269
|
+
readonly line: "cbmm";
|
|
270
|
+
readonly category: "collections";
|
|
271
|
+
};
|
|
272
|
+
readonly offramp_treasury_credit: {
|
|
273
|
+
readonly creditDebitType: "credit";
|
|
274
|
+
readonly code: "offramp_treasury_credit";
|
|
275
|
+
readonly line: "stable";
|
|
276
|
+
readonly category: "conversions";
|
|
277
|
+
};
|
|
278
|
+
readonly onramp_debit: {
|
|
279
|
+
readonly creditDebitType: "debit";
|
|
280
|
+
readonly code: "onramp_debit";
|
|
281
|
+
readonly line: "stable";
|
|
282
|
+
readonly category: "conversions";
|
|
283
|
+
};
|
|
284
|
+
readonly offramp_credit: {
|
|
285
|
+
readonly creditDebitType: "credit";
|
|
286
|
+
readonly code: "offramp_credit";
|
|
287
|
+
readonly line: "stable";
|
|
288
|
+
readonly category: "conversions";
|
|
289
|
+
};
|
|
266
290
|
};
|
|
267
291
|
export type TransactionType = keyof typeof TRANSACTION_TYPES;
|
|
268
292
|
export type TransactionTypeRecord = (typeof TRANSACTION_TYPES)[TransactionType];
|
|
@@ -266,4 +266,28 @@ exports.TRANSACTION_TYPES = {
|
|
|
266
266
|
line: "cbmm",
|
|
267
267
|
category: "collections",
|
|
268
268
|
},
|
|
269
|
+
global_rejected: {
|
|
270
|
+
creditDebitType: "credit",
|
|
271
|
+
code: "global_rejected",
|
|
272
|
+
line: "cbmm",
|
|
273
|
+
category: "collections",
|
|
274
|
+
},
|
|
275
|
+
offramp_treasury_credit: {
|
|
276
|
+
creditDebitType: "credit",
|
|
277
|
+
code: "offramp_treasury_credit",
|
|
278
|
+
line: "stable",
|
|
279
|
+
category: "conversions",
|
|
280
|
+
},
|
|
281
|
+
onramp_debit: {
|
|
282
|
+
creditDebitType: "debit",
|
|
283
|
+
code: "onramp_debit",
|
|
284
|
+
line: "stable",
|
|
285
|
+
category: "conversions",
|
|
286
|
+
},
|
|
287
|
+
offramp_credit: {
|
|
288
|
+
creditDebitType: "credit",
|
|
289
|
+
code: "offramp_credit",
|
|
290
|
+
line: "stable",
|
|
291
|
+
category: "conversions",
|
|
292
|
+
},
|
|
269
293
|
};
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"stable_payout_debit": "Stablecoin payment · Direct · debit",
|
|
44
44
|
"stable_payout_credit": "Stablecoin payment · Direct · credit",
|
|
45
45
|
"global_pay_to_generic_debit": "International payment",
|
|
46
|
-
"global_pay_to_generic_credit": "International payment return"
|
|
46
|
+
"global_pay_to_generic_credit": "International payment return",
|
|
47
|
+
"global_rejected": "Rejection · Global",
|
|
48
|
+
"offramp_treasury_credit": "Treasury conversion · Off-Ramp",
|
|
49
|
+
"onramp_debit": "Conversion to stablecoin · On-Ramp · debit",
|
|
50
|
+
"offramp_credit": "Conversion to local currency · Off-Ramp · credit"
|
|
47
51
|
}
|
|
48
52
|
}
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"stable_payout_debit": "Pago en stablecoin · Directo · débito",
|
|
44
44
|
"stable_payout_credit": "Pago en stablecoin · Directo · crédito",
|
|
45
45
|
"global_pay_to_generic_debit": "Pago internacional",
|
|
46
|
-
"global_pay_to_generic_credit": "Retorno de pago internacional"
|
|
46
|
+
"global_pay_to_generic_credit": "Retorno de pago internacional",
|
|
47
|
+
"global_rejected": "Rechazo · Global",
|
|
48
|
+
"offramp_treasury_credit": "Conversión de tesorería · Off-Ramp",
|
|
49
|
+
"onramp_debit": "Conversión a stablecoin · On-Ramp · débito",
|
|
50
|
+
"offramp_credit": "Conversión a moneda local · Off-Ramp · crédito"
|
|
47
51
|
}
|
|
48
52
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export * from "./interfaces/common.interface";
|
|
2
|
+
export * from "./interfaces/translation.interface";
|
|
2
3
|
export * from "./interfaces/request.interface";
|
|
3
4
|
export * from "./interfaces/response.interface";
|
|
5
|
+
export * from "./utils/search-filter.utils";
|
|
4
6
|
export const EXPORT_FORMATS = {
|
|
5
7
|
CSV: "csv",
|
|
6
8
|
JSON: "json"
|
|
7
9
|
};
|
|
8
10
|
/**
|
|
9
11
|
* The export engine's real allow-list (verified against util-platform-exports'
|
|
10
|
-
* QueryOperator enum). A layout's catalog
|
|
11
|
-
*
|
|
12
|
+
* QueryOperator enum). `in` and `between` take comma-separated values. A layout's catalog declares which of
|
|
13
|
+
* them each column accepts, so a consumer validating a filter needs both lists.
|
|
12
14
|
*/
|
|
13
15
|
export const EXPORT_QUERY_OPERATORS = {
|
|
14
16
|
EQ: "eq",
|
|
@@ -16,5 +18,8 @@ export const EXPORT_QUERY_OPERATORS = {
|
|
|
16
18
|
GT: "gt",
|
|
17
19
|
LT: "lt",
|
|
18
20
|
LTE: "lte",
|
|
19
|
-
GTE: "gte"
|
|
21
|
+
GTE: "gte",
|
|
22
|
+
IN: "in",
|
|
23
|
+
LIKE: "like",
|
|
24
|
+
BETWEEN: "between"
|
|
20
25
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* Literals, not EXPORT_QUERY_OPERATORS: a value import from the barrel that re-exports this file is a runtime cycle. */
|
|
2
|
+
const SEARCH_TO_EXPORT_OPERATORS = {
|
|
3
|
+
eq: "eq",
|
|
4
|
+
not_eq: "neq",
|
|
5
|
+
contains: "like",
|
|
6
|
+
gte: "gte",
|
|
7
|
+
lte: "lte",
|
|
8
|
+
};
|
|
9
|
+
/* The export splits an `in` value on commas, so a value that holds one can't travel in it. */
|
|
10
|
+
const IN_SEPARATOR = ",";
|
|
11
|
+
const isAndFilter = (filter) => "and" in filter;
|
|
12
|
+
const isOrFilter = (part) => "or" in part;
|
|
13
|
+
const toColumn = (field, { fieldMap = {} }) => fieldMap[field] ?? field;
|
|
14
|
+
const toCondition = ({ field, operator, value }, options) => {
|
|
15
|
+
const exportOperator = SEARCH_TO_EXPORT_OPERATORS[operator];
|
|
16
|
+
const exportValue = String(value);
|
|
17
|
+
/* A blank contains matches every row with a value in the search; the export rejects a blank like and has no exists. */
|
|
18
|
+
if (!exportOperator || (exportOperator === "like" && exportValue.trim() === ""))
|
|
19
|
+
return null;
|
|
20
|
+
return { column: toColumn(field, options), operator: exportOperator, value: exportValue };
|
|
21
|
+
};
|
|
22
|
+
/* A multi-select filter reaches the search as an OR of eq on one field; the export reads it as in. */
|
|
23
|
+
const toInCondition = ({ or }, options) => {
|
|
24
|
+
const field = or[0]?.field;
|
|
25
|
+
const values = or.map(({ value }) => String(value));
|
|
26
|
+
const isSingleFieldEq = field !== undefined && or.every(part => part.operator === "eq" && part.field === field);
|
|
27
|
+
if (!isSingleFieldEq || values.some(value => value.includes(IN_SEPARATOR)))
|
|
28
|
+
return null;
|
|
29
|
+
return { column: toColumn(field, options), operator: "in", value: values.join(IN_SEPARATOR) };
|
|
30
|
+
};
|
|
31
|
+
const toPartCondition = (part, options) => {
|
|
32
|
+
if (!isOrFilter(part))
|
|
33
|
+
return toCondition(part, options);
|
|
34
|
+
const [singleCondition] = part.or;
|
|
35
|
+
return part.or.length === 1 && singleCondition ? toCondition(singleCondition, options) : toInCondition(part, options);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Translates the filter a table sends to the search API into the conditions the exports API takes, so an
|
|
39
|
+
* export can match what the table shows. What has no export equivalent comes back in `untranslatable`.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* toExportFilterConditions({
|
|
43
|
+
* and: [
|
|
44
|
+
* { or: [{ field: "state", operator: "eq", value: "completed" }, { field: "state", operator: "eq", value: "failed" }] },
|
|
45
|
+
* { field: "created_at", operator: "gte", value: "2026-10-01T00:00:00" },
|
|
46
|
+
* ],
|
|
47
|
+
* })
|
|
48
|
+
* // { conditions: [
|
|
49
|
+
* // { column: "state", operator: "in", value: "completed,failed" },
|
|
50
|
+
* // { column: "created_at", operator: "gte", value: "2026-10-01T00:00:00" }
|
|
51
|
+
* // ], untranslatable: [] }
|
|
52
|
+
*/
|
|
53
|
+
export const toExportFilterConditions = (filter, options = {}) => {
|
|
54
|
+
const parts = isAndFilter(filter) ? filter.and : [filter];
|
|
55
|
+
const translation = { conditions: [], untranslatable: [] };
|
|
56
|
+
parts.forEach(part => {
|
|
57
|
+
const condition = toPartCondition(part, options);
|
|
58
|
+
if (condition)
|
|
59
|
+
translation.conditions.push(condition);
|
|
60
|
+
else
|
|
61
|
+
translation.untranslatable.push(part);
|
|
62
|
+
});
|
|
63
|
+
return translation;
|
|
64
|
+
};
|
|
@@ -23,4 +23,6 @@ export const MOVEMENT_TYPES = {
|
|
|
23
23
|
stable_payout: { code: "stable_payout", line: "stable", category: "conversions", group: "stablecoin", groupLabel: "Stablecoin" },
|
|
24
24
|
breb: { code: "breb", line: "payout", category: "payments" },
|
|
25
25
|
global_pay_to_generic: { code: "global_pay_to_generic", line: "payout", category: "payments" },
|
|
26
|
+
swift: { code: "swift", line: "payout", category: "payments" },
|
|
27
|
+
sepa: { code: "sepa", line: "payout", category: "payments" },
|
|
26
28
|
};
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"cross_border": "International",
|
|
24
24
|
"stable_payout": "Stablecoin payment · Direct",
|
|
25
25
|
"breb": "Instant payment · Bre-B",
|
|
26
|
-
"global_pay_to_generic": "International payment"
|
|
26
|
+
"global_pay_to_generic": "International payment",
|
|
27
|
+
"swift": "International transfer · Swift",
|
|
28
|
+
"sepa": "International transfer · SEPA"
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"cross_border": "Internacional",
|
|
24
24
|
"stable_payout": "Pago en stablecoin · Directo",
|
|
25
25
|
"breb": "Pago instantáneo · Bre-B",
|
|
26
|
-
"global_pay_to_generic": "Pago internacional"
|
|
26
|
+
"global_pay_to_generic": "Pago internacional",
|
|
27
|
+
"swift": "Transferencia internacional · Swift",
|
|
28
|
+
"sepa": "Transferencia internacional · SEPA"
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -16,7 +16,7 @@ export const getMovementTypes = (lang) => {
|
|
|
16
16
|
* Filter by `group`, not `groupLabel` — the label is display text, not a stable filter key.
|
|
17
17
|
*
|
|
18
18
|
* `group` is optional on the record, so `group: undefined` is a present key like any other — it
|
|
19
|
-
* matches the
|
|
19
|
+
* matches the records that don't carry a group, not "no filter". A UI filter fed from an optional
|
|
20
20
|
* ref has to be spread in conditionally: `{ lang, ...(group ? { group } : {}) }`.
|
|
21
21
|
*/
|
|
22
22
|
export const queryMovementTypes = (query) => {
|
|
@@ -263,4 +263,28 @@ export const TRANSACTION_TYPES = {
|
|
|
263
263
|
line: "cbmm",
|
|
264
264
|
category: "collections",
|
|
265
265
|
},
|
|
266
|
+
global_rejected: {
|
|
267
|
+
creditDebitType: "credit",
|
|
268
|
+
code: "global_rejected",
|
|
269
|
+
line: "cbmm",
|
|
270
|
+
category: "collections",
|
|
271
|
+
},
|
|
272
|
+
offramp_treasury_credit: {
|
|
273
|
+
creditDebitType: "credit",
|
|
274
|
+
code: "offramp_treasury_credit",
|
|
275
|
+
line: "stable",
|
|
276
|
+
category: "conversions",
|
|
277
|
+
},
|
|
278
|
+
onramp_debit: {
|
|
279
|
+
creditDebitType: "debit",
|
|
280
|
+
code: "onramp_debit",
|
|
281
|
+
line: "stable",
|
|
282
|
+
category: "conversions",
|
|
283
|
+
},
|
|
284
|
+
offramp_credit: {
|
|
285
|
+
creditDebitType: "credit",
|
|
286
|
+
code: "offramp_credit",
|
|
287
|
+
line: "stable",
|
|
288
|
+
category: "conversions",
|
|
289
|
+
},
|
|
266
290
|
};
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"stable_payout_debit": "Stablecoin payment · Direct · debit",
|
|
44
44
|
"stable_payout_credit": "Stablecoin payment · Direct · credit",
|
|
45
45
|
"global_pay_to_generic_debit": "International payment",
|
|
46
|
-
"global_pay_to_generic_credit": "International payment return"
|
|
46
|
+
"global_pay_to_generic_credit": "International payment return",
|
|
47
|
+
"global_rejected": "Rejection · Global",
|
|
48
|
+
"offramp_treasury_credit": "Treasury conversion · Off-Ramp",
|
|
49
|
+
"onramp_debit": "Conversion to stablecoin · On-Ramp · debit",
|
|
50
|
+
"offramp_credit": "Conversion to local currency · Off-Ramp · credit"
|
|
47
51
|
}
|
|
48
52
|
}
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"stable_payout_debit": "Pago en stablecoin · Directo · débito",
|
|
44
44
|
"stable_payout_credit": "Pago en stablecoin · Directo · crédito",
|
|
45
45
|
"global_pay_to_generic_debit": "Pago internacional",
|
|
46
|
-
"global_pay_to_generic_credit": "Retorno de pago internacional"
|
|
46
|
+
"global_pay_to_generic_credit": "Retorno de pago internacional",
|
|
47
|
+
"global_rejected": "Rechazo · Global",
|
|
48
|
+
"offramp_treasury_credit": "Conversión de tesorería · Off-Ramp",
|
|
49
|
+
"onramp_debit": "Conversión a stablecoin · On-Ramp · débito",
|
|
50
|
+
"offramp_credit": "Conversión a moneda local · Off-Ramp · crédito"
|
|
47
51
|
}
|
|
48
52
|
}
|