@aderis-erp/cli 1.0.3 → 1.0.7
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 +6 -0
- package/dist/commands/access-groups.d.ts +1 -0
- package/dist/commands/access-groups.js +84 -28
- package/dist/commands/{async-endpoints-internal.d.ts → account-transaction-allocations.d.ts} +3 -2
- package/dist/commands/{async-endpoints-internal.js → account-transaction-allocations.js} +93 -33
- package/dist/commands/account-transactions.d.ts +1 -0
- package/dist/commands/account-transactions.js +96 -40
- package/dist/commands/accounts.d.ts +1 -0
- package/dist/commands/accounts.js +89 -33
- package/dist/commands/agent-runs.d.ts +1 -0
- package/dist/commands/agent-runs.js +85 -27
- package/dist/commands/audit-log.d.ts +1 -0
- package/dist/commands/audit-log.js +82 -26
- package/dist/commands/auth/login.js +1 -2
- package/dist/commands/auth/tenant.d.ts +1 -1
- package/dist/commands/auth/tenant.js +11 -11
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bank-accounts.d.ts +1 -0
- package/dist/commands/bank-accounts.js +96 -40
- package/dist/commands/bank-integrations.d.ts +1 -0
- package/dist/commands/bank-integrations.js +97 -41
- package/dist/commands/business-partners---contacts.d.ts +1 -0
- package/dist/commands/business-partners---contacts.js +89 -33
- package/dist/commands/business-partners.d.ts +1 -0
- package/dist/commands/business-partners.js +89 -33
- package/dist/commands/credit-card-accounts.d.ts +1 -0
- package/dist/commands/credit-card-accounts.js +94 -38
- package/dist/commands/documents.d.ts +1 -0
- package/dist/commands/documents.js +85 -29
- package/dist/commands/dunning-configs.d.ts +1 -0
- package/dist/commands/dunning-configs.js +84 -28
- package/dist/commands/dunning-reminders.d.ts +1 -0
- package/dist/commands/dunning-reminders.js +87 -31
- package/dist/commands/dunning-runs.d.ts +1 -0
- package/dist/commands/dunning-runs.js +80 -24
- package/dist/commands/employees.d.ts +1 -0
- package/dist/commands/employees.js +111 -55
- package/dist/commands/event-processor-mappings.d.ts +1 -0
- package/dist/commands/event-processor-mappings.js +84 -28
- package/dist/commands/files-internal.d.ts +1 -0
- package/dist/commands/files-internal.js +88 -32
- package/dist/commands/health.d.ts +1 -0
- package/dist/commands/health.js +84 -28
- package/dist/commands/inbox.d.ts +1 -0
- package/dist/commands/inbox.js +84 -28
- package/dist/commands/integrations.d.ts +1 -0
- package/dist/commands/integrations.js +82 -26
- package/dist/commands/item-categories.d.ts +1 -0
- package/dist/commands/item-categories.js +84 -28
- package/dist/commands/items.d.ts +1 -0
- package/dist/commands/items.js +84 -28
- package/dist/commands/journal-lines.d.ts +1 -0
- package/dist/commands/journal-lines.js +80 -24
- package/dist/commands/journals.d.ts +1 -0
- package/dist/commands/journals.js +98 -42
- package/dist/commands/ledgers.d.ts +1 -0
- package/dist/commands/ledgers.js +95 -39
- package/dist/commands/office-locations.d.ts +1 -0
- package/dist/commands/office-locations.js +84 -28
- package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
- package/dist/commands/payment-method-ledger-mappings.js +84 -28
- package/dist/commands/payment-methods.d.ts +1 -0
- package/dist/commands/payment-methods.js +84 -28
- package/dist/commands/payment-runs.d.ts +1 -0
- package/dist/commands/payment-runs.js +97 -41
- package/dist/commands/payment-terms.d.ts +1 -0
- package/dist/commands/payment-terms.js +85 -29
- package/dist/commands/price-lists.d.ts +1 -0
- package/dist/commands/price-lists.js +89 -33
- package/dist/commands/processor-mappings.d.ts +1 -0
- package/dist/commands/processor-mappings.js +83 -27
- package/dist/commands/processors.d.ts +1 -0
- package/dist/commands/processors.js +88 -32
- package/dist/commands/reconciliation.d.ts +1 -0
- package/dist/commands/reconciliation.js +85 -29
- package/dist/commands/reports.d.ts +1 -0
- package/dist/commands/reports.js +85 -26
- package/dist/commands/salary-types.d.ts +1 -0
- package/dist/commands/salary-types.js +84 -28
- package/dist/commands/sales-invoices.d.ts +1 -0
- package/dist/commands/sales-invoices.js +109 -53
- package/dist/commands/sales-orders.d.ts +1 -0
- package/dist/commands/sales-orders.js +107 -51
- package/dist/commands/tags.d.ts +1 -0
- package/dist/commands/tags.js +84 -28
- package/dist/commands/tax-codes.d.ts +1 -0
- package/dist/commands/tax-codes.js +87 -31
- package/dist/commands/tax-matrix.d.ts +1 -0
- package/dist/commands/tax-matrix.js +84 -28
- package/dist/commands/tenants.d.ts +1 -0
- package/dist/commands/tenants.js +84 -28
- package/dist/commands/text-templates.d.ts +1 -0
- package/dist/commands/text-templates.js +84 -28
- package/dist/commands/tools.d.ts +1 -0
- package/dist/commands/tools.js +81 -25
- package/dist/commands/vat-declaration.d.ts +41 -0
- package/dist/commands/vat-declaration.js +1079 -0
- package/dist/commands/vendor-invoices.d.ts +1 -0
- package/dist/commands/vendor-invoices.js +113 -57
- package/dist/commands/webhooks.d.ts +1 -0
- package/dist/commands/webhooks.js +86 -30
- package/dist/commands/workflows.d.ts +1 -0
- package/dist/commands/workflows.js +82 -26
- package/dist/lib/api-client.d.ts +6 -1
- package/dist/lib/api-client.js +23 -5
- package/dist/lib/auth.js +18 -0
- package/dist/lib/config.js +1 -1
- package/oclif.manifest.json +229 -52
- package/package.json +15 -13
- package/dist/index 2.js +0 -1
- package/dist/index.d 2.ts +0 -1
|
@@ -8,15 +8,15 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list-tools", "list", "create", "resolve", "resolve-event", "get", "update", "delete", "template"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false },
|
|
12
|
-
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false },
|
|
13
|
-
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false },
|
|
14
|
-
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false },
|
|
15
|
-
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false },
|
|
19
|
-
'template': { method: 'POST', path: '/v1/processors/{id}/template', summary: 'Upload a PDF template for a processor', pathParams: ['id'], hasBody: false, isMultipart: false },
|
|
11
|
+
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
14
|
+
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
+
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
+
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
+
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
18
|
+
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
+
'template': { method: 'POST', path: '/v1/processors/{id}/template', summary: 'Upload a PDF template for a processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
20
|
};
|
|
21
21
|
export default class Processors extends Command {
|
|
22
22
|
static description = 'Manage processors';
|
|
@@ -85,11 +85,6 @@ export default class Processors extends Command {
|
|
|
85
85
|
const endpoint = ENDPOINTS[args.action];
|
|
86
86
|
if (!endpoint)
|
|
87
87
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
88
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
89
|
-
if (!context)
|
|
90
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
91
|
-
if (!context.tokens)
|
|
92
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
93
88
|
let apiPath = endpoint.path;
|
|
94
89
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
95
90
|
const param = endpoint.pathParams[i];
|
|
@@ -108,6 +103,11 @@ export default class Processors extends Command {
|
|
|
108
103
|
}
|
|
109
104
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
110
105
|
}
|
|
106
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
107
|
+
if (!context)
|
|
108
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
109
|
+
if (!context.tokens)
|
|
110
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
111
111
|
const queryParams = {};
|
|
112
112
|
if (flags.page)
|
|
113
113
|
queryParams.page = flags.page;
|
|
@@ -134,15 +134,7 @@ export default class Processors extends Command {
|
|
|
134
134
|
if (eqIndex === -1)
|
|
135
135
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
136
136
|
const key = f.slice(0, eqIndex);
|
|
137
|
-
|
|
138
|
-
// Check for operator prefix (with or without $)
|
|
139
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
140
|
-
if (opMatch) {
|
|
141
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
value = `\$eq:${value}`;
|
|
145
|
-
}
|
|
137
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
146
138
|
const filterKey = `filter.${key}`;
|
|
147
139
|
if (filterKey in queryParams) {
|
|
148
140
|
// Multiple filters on same field: collect as array
|
|
@@ -175,7 +167,9 @@ export default class Processors extends Command {
|
|
|
175
167
|
data = form;
|
|
176
168
|
}
|
|
177
169
|
else if (endpoint.hasBody) {
|
|
178
|
-
|
|
170
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
171
|
+
// input file — skip the read-as-body branch.
|
|
172
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
179
173
|
const fsModule = await import('node:fs');
|
|
180
174
|
try {
|
|
181
175
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -197,6 +191,7 @@ export default class Processors extends Command {
|
|
|
197
191
|
if (!data)
|
|
198
192
|
data = {};
|
|
199
193
|
const body = data;
|
|
194
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
200
195
|
for (const pair of flags.set) {
|
|
201
196
|
const eqIndex = pair.indexOf('=');
|
|
202
197
|
if (eqIndex === -1)
|
|
@@ -208,6 +203,19 @@ export default class Processors extends Command {
|
|
|
208
203
|
value = JSON.parse(value);
|
|
209
204
|
}
|
|
210
205
|
catch { /* keep as string */ }
|
|
206
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
207
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
208
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
209
|
+
// already provided a JSON array.
|
|
210
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
211
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
212
|
+
if (typeof value === 'string') {
|
|
213
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
214
|
+
}
|
|
215
|
+
else if (value !== undefined && value !== null) {
|
|
216
|
+
value = [value];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
211
219
|
// Support nested keys like "address.city"
|
|
212
220
|
const keys = key.split('.');
|
|
213
221
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -258,6 +266,26 @@ export default class Processors extends Command {
|
|
|
258
266
|
process.stderr.write('\n');
|
|
259
267
|
}
|
|
260
268
|
try {
|
|
269
|
+
// Binary response: download to file
|
|
270
|
+
if (endpoint.isBinaryResponse) {
|
|
271
|
+
if (!flags.file) {
|
|
272
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
273
|
+
}
|
|
274
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
275
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
276
|
+
data,
|
|
277
|
+
contextName: flags.context,
|
|
278
|
+
binary: true,
|
|
279
|
+
});
|
|
280
|
+
const fsModule = await import('node:fs');
|
|
281
|
+
const binData = binResponse.data;
|
|
282
|
+
const buffer = Buffer.isBuffer(binData)
|
|
283
|
+
? binData
|
|
284
|
+
: Buffer.from(binData);
|
|
285
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
286
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
261
289
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
262
290
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
263
291
|
data,
|
|
@@ -348,6 +376,7 @@ export default class Processors extends Command {
|
|
|
348
376
|
}
|
|
349
377
|
else if (flags.set && flags.set.length > 0) {
|
|
350
378
|
const item = {};
|
|
379
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
351
380
|
for (const pair of flags.set) {
|
|
352
381
|
const eqIndex = pair.indexOf('=');
|
|
353
382
|
if (eqIndex === -1)
|
|
@@ -358,6 +387,15 @@ export default class Processors extends Command {
|
|
|
358
387
|
value = JSON.parse(value);
|
|
359
388
|
}
|
|
360
389
|
catch { /* keep as string */ }
|
|
390
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
391
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
392
|
+
if (typeof value === 'string') {
|
|
393
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
394
|
+
}
|
|
395
|
+
else if (value !== undefined && value !== null) {
|
|
396
|
+
value = [value];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
361
399
|
const keys = key.split('.');
|
|
362
400
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
363
401
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -507,14 +545,7 @@ export default class Processors extends Command {
|
|
|
507
545
|
if (eqIndex === -1)
|
|
508
546
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
509
547
|
const key = f.slice(0, eqIndex);
|
|
510
|
-
|
|
511
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
512
|
-
if (opMatch) {
|
|
513
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
value = `\$eq:${value}`;
|
|
517
|
-
}
|
|
548
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
518
549
|
const filterKey = `filter.${key}`;
|
|
519
550
|
if (filterKey in baseParams) {
|
|
520
551
|
const existing = baseParams[filterKey];
|
|
@@ -691,6 +722,7 @@ export default class Processors extends Command {
|
|
|
691
722
|
}
|
|
692
723
|
else if (flags.set && flags.set.length > 0) {
|
|
693
724
|
const item = {};
|
|
725
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
694
726
|
for (const pair of flags.set) {
|
|
695
727
|
const eqIndex = pair.indexOf('=');
|
|
696
728
|
if (eqIndex === -1)
|
|
@@ -701,6 +733,15 @@ export default class Processors extends Command {
|
|
|
701
733
|
value = JSON.parse(value);
|
|
702
734
|
}
|
|
703
735
|
catch { /* keep as string */ }
|
|
736
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
737
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
738
|
+
if (typeof value === 'string') {
|
|
739
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
740
|
+
}
|
|
741
|
+
else if (value !== undefined && value !== null) {
|
|
742
|
+
value = [value];
|
|
743
|
+
}
|
|
744
|
+
}
|
|
704
745
|
const keys = key.split('.');
|
|
705
746
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
706
747
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -820,6 +861,21 @@ export default class Processors extends Command {
|
|
|
820
861
|
this.log('');
|
|
821
862
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
822
863
|
}
|
|
864
|
+
parseFilterValue(value) {
|
|
865
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
866
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
867
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
868
|
+
// no operator prefix is present.
|
|
869
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
870
|
+
const m = value.match(opRe);
|
|
871
|
+
if (!m)
|
|
872
|
+
return `\$eq:${value}`;
|
|
873
|
+
const op = m[1];
|
|
874
|
+
const rest = value.slice(m[0].length);
|
|
875
|
+
if (rest === '')
|
|
876
|
+
return `\$${op}`;
|
|
877
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
878
|
+
}
|
|
823
879
|
filterFields(data, fields) {
|
|
824
880
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
825
881
|
const pick = (obj, keys) => {
|
|
@@ -8,12 +8,12 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "transactions-matches", "transactions-summary", "matches-confirm", "matches-reject", "process-transaction"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/reconciliation/pending', summary: 'Get pending reconciliation matches', pathParams: [], hasBody: false, isMultipart: false },
|
|
12
|
-
'transactions-matches': { method: 'GET', path: '/v1/reconciliation/transactions/{transactionId}/matches', summary: 'Get matches for a transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false },
|
|
13
|
-
'transactions-summary': { method: 'GET', path: '/v1/reconciliation/transactions/{transactionId}/summary', summary: 'Get transaction allocation summary', pathParams: ['transactionId'], hasBody: false, isMultipart: false },
|
|
14
|
-
'matches-confirm': { method: 'POST', path: '/v1/reconciliation/matches/{matchId}/confirm', summary: 'Confirm a reconciliation match', pathParams: ['matchId'], hasBody: false, isMultipart: false },
|
|
15
|
-
'matches-reject': { method: 'POST', path: '/v1/reconciliation/matches/{matchId}/reject', summary: 'Reject a reconciliation match', pathParams: ['matchId'], hasBody: true, isMultipart: false },
|
|
16
|
-
'process-transaction': { method: 'POST', path: '/v1/reconciliation/transactions/{transactionId}/process', summary: 'Trigger reconciliation for a transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/reconciliation/pending', summary: 'Get pending reconciliation matches', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'transactions-matches': { method: 'GET', path: '/v1/reconciliation/transactions/{transactionId}/matches', summary: 'Get matches for a transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'transactions-summary': { method: 'GET', path: '/v1/reconciliation/transactions/{transactionId}/summary', summary: 'Get transaction allocation summary', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
+
'matches-confirm': { method: 'POST', path: '/v1/reconciliation/matches/{matchId}/confirm', summary: 'Confirm a reconciliation match', pathParams: ['matchId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
+
'matches-reject': { method: 'POST', path: '/v1/reconciliation/matches/{matchId}/reject', summary: 'Reject a reconciliation match', pathParams: ['matchId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
+
'process-transaction': { method: 'POST', path: '/v1/reconciliation/transactions/{transactionId}/process', summary: 'Trigger reconciliation for a transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
17
|
};
|
|
18
18
|
export default class Reconciliation extends Command {
|
|
19
19
|
static description = 'Manage reconciliation';
|
|
@@ -78,11 +78,6 @@ export default class Reconciliation extends Command {
|
|
|
78
78
|
const endpoint = ENDPOINTS[args.action];
|
|
79
79
|
if (!endpoint)
|
|
80
80
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
81
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
82
|
-
if (!context)
|
|
83
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
84
|
-
if (!context.tokens)
|
|
85
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
86
81
|
let apiPath = endpoint.path;
|
|
87
82
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
88
83
|
const param = endpoint.pathParams[i];
|
|
@@ -101,6 +96,11 @@ export default class Reconciliation extends Command {
|
|
|
101
96
|
}
|
|
102
97
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
103
98
|
}
|
|
99
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
100
|
+
if (!context)
|
|
101
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
102
|
+
if (!context.tokens)
|
|
103
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
104
104
|
const queryParams = {};
|
|
105
105
|
if (flags.page)
|
|
106
106
|
queryParams.page = flags.page;
|
|
@@ -127,15 +127,7 @@ export default class Reconciliation extends Command {
|
|
|
127
127
|
if (eqIndex === -1)
|
|
128
128
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
129
129
|
const key = f.slice(0, eqIndex);
|
|
130
|
-
|
|
131
|
-
// Check for operator prefix (with or without $)
|
|
132
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
133
|
-
if (opMatch) {
|
|
134
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
value = `\$eq:${value}`;
|
|
138
|
-
}
|
|
130
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
139
131
|
const filterKey = `filter.${key}`;
|
|
140
132
|
if (filterKey in queryParams) {
|
|
141
133
|
// Multiple filters on same field: collect as array
|
|
@@ -168,7 +160,9 @@ export default class Reconciliation extends Command {
|
|
|
168
160
|
data = form;
|
|
169
161
|
}
|
|
170
162
|
else if (endpoint.hasBody) {
|
|
171
|
-
|
|
163
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
164
|
+
// input file — skip the read-as-body branch.
|
|
165
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
172
166
|
const fsModule = await import('node:fs');
|
|
173
167
|
try {
|
|
174
168
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -190,6 +184,7 @@ export default class Reconciliation extends Command {
|
|
|
190
184
|
if (!data)
|
|
191
185
|
data = {};
|
|
192
186
|
const body = data;
|
|
187
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
193
188
|
for (const pair of flags.set) {
|
|
194
189
|
const eqIndex = pair.indexOf('=');
|
|
195
190
|
if (eqIndex === -1)
|
|
@@ -201,6 +196,19 @@ export default class Reconciliation extends Command {
|
|
|
201
196
|
value = JSON.parse(value);
|
|
202
197
|
}
|
|
203
198
|
catch { /* keep as string */ }
|
|
199
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
200
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
201
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
202
|
+
// already provided a JSON array.
|
|
203
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
204
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
205
|
+
if (typeof value === 'string') {
|
|
206
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
207
|
+
}
|
|
208
|
+
else if (value !== undefined && value !== null) {
|
|
209
|
+
value = [value];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
204
212
|
// Support nested keys like "address.city"
|
|
205
213
|
const keys = key.split('.');
|
|
206
214
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -251,6 +259,26 @@ export default class Reconciliation extends Command {
|
|
|
251
259
|
process.stderr.write('\n');
|
|
252
260
|
}
|
|
253
261
|
try {
|
|
262
|
+
// Binary response: download to file
|
|
263
|
+
if (endpoint.isBinaryResponse) {
|
|
264
|
+
if (!flags.file) {
|
|
265
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
266
|
+
}
|
|
267
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
268
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
269
|
+
data,
|
|
270
|
+
contextName: flags.context,
|
|
271
|
+
binary: true,
|
|
272
|
+
});
|
|
273
|
+
const fsModule = await import('node:fs');
|
|
274
|
+
const binData = binResponse.data;
|
|
275
|
+
const buffer = Buffer.isBuffer(binData)
|
|
276
|
+
? binData
|
|
277
|
+
: Buffer.from(binData);
|
|
278
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
279
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
254
282
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
255
283
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
256
284
|
data,
|
|
@@ -341,6 +369,7 @@ export default class Reconciliation extends Command {
|
|
|
341
369
|
}
|
|
342
370
|
else if (flags.set && flags.set.length > 0) {
|
|
343
371
|
const item = {};
|
|
372
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
344
373
|
for (const pair of flags.set) {
|
|
345
374
|
const eqIndex = pair.indexOf('=');
|
|
346
375
|
if (eqIndex === -1)
|
|
@@ -351,6 +380,15 @@ export default class Reconciliation extends Command {
|
|
|
351
380
|
value = JSON.parse(value);
|
|
352
381
|
}
|
|
353
382
|
catch { /* keep as string */ }
|
|
383
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
384
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
385
|
+
if (typeof value === 'string') {
|
|
386
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
387
|
+
}
|
|
388
|
+
else if (value !== undefined && value !== null) {
|
|
389
|
+
value = [value];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
354
392
|
const keys = key.split('.');
|
|
355
393
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
356
394
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -500,14 +538,7 @@ export default class Reconciliation extends Command {
|
|
|
500
538
|
if (eqIndex === -1)
|
|
501
539
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
502
540
|
const key = f.slice(0, eqIndex);
|
|
503
|
-
|
|
504
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
505
|
-
if (opMatch) {
|
|
506
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
value = `\$eq:${value}`;
|
|
510
|
-
}
|
|
541
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
511
542
|
const filterKey = `filter.${key}`;
|
|
512
543
|
if (filterKey in baseParams) {
|
|
513
544
|
const existing = baseParams[filterKey];
|
|
@@ -684,6 +715,7 @@ export default class Reconciliation extends Command {
|
|
|
684
715
|
}
|
|
685
716
|
else if (flags.set && flags.set.length > 0) {
|
|
686
717
|
const item = {};
|
|
718
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
687
719
|
for (const pair of flags.set) {
|
|
688
720
|
const eqIndex = pair.indexOf('=');
|
|
689
721
|
if (eqIndex === -1)
|
|
@@ -694,6 +726,15 @@ export default class Reconciliation extends Command {
|
|
|
694
726
|
value = JSON.parse(value);
|
|
695
727
|
}
|
|
696
728
|
catch { /* keep as string */ }
|
|
729
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
730
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
731
|
+
if (typeof value === 'string') {
|
|
732
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
733
|
+
}
|
|
734
|
+
else if (value !== undefined && value !== null) {
|
|
735
|
+
value = [value];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
697
738
|
const keys = key.split('.');
|
|
698
739
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
699
740
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -813,6 +854,21 @@ export default class Reconciliation extends Command {
|
|
|
813
854
|
this.log('');
|
|
814
855
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
815
856
|
}
|
|
857
|
+
parseFilterValue(value) {
|
|
858
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
859
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
860
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
861
|
+
// no operator prefix is present.
|
|
862
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
863
|
+
const m = value.match(opRe);
|
|
864
|
+
if (!m)
|
|
865
|
+
return `\$eq:${value}`;
|
|
866
|
+
const op = m[1];
|
|
867
|
+
const rest = value.slice(m[0].length);
|
|
868
|
+
if (rest === '')
|
|
869
|
+
return `\$${op}`;
|
|
870
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
871
|
+
}
|
|
816
872
|
filterFields(data, fields) {
|
|
817
873
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
818
874
|
const pick = (obj, keys) => {
|
package/dist/commands/reports.js
CHANGED
|
@@ -6,10 +6,13 @@ const HAS_IMPORT = false;
|
|
|
6
6
|
const HAS_EXPORT = false;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [...["profit-and-loss", "balance-sheet"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
9
|
+
const SUBCOMMANDS = [...["profit-and-loss", "balance-sheet", "gl-detail", "gl-detail-export-xlsx", "gl-detail-export-pdf"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'profit-and-loss': { method: 'GET', path: '/v1/reports/profit-and-loss', summary: 'Get Profit & Loss report', pathParams: [], hasBody: false, isMultipart: false },
|
|
12
|
-
'balance-sheet': { method: 'GET', path: '/v1/reports/balance-sheet', summary: 'Generate Balance Sheet report', pathParams: [], hasBody: false, isMultipart: false },
|
|
11
|
+
'profit-and-loss': { method: 'GET', path: '/v1/reports/profit-and-loss', summary: 'Get Profit & Loss report', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'balance-sheet': { method: 'GET', path: '/v1/reports/balance-sheet', summary: 'Generate Balance Sheet report', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'gl-detail': { method: 'POST', path: '/v1/reports/gl-detail', summary: 'Get GL Detail report', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
14
|
+
'gl-detail-export-xlsx': { method: 'POST', path: '/v1/reports/gl-detail/export/xlsx', summary: 'Export GL Detail report as Excel (one sheet per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
15
|
+
'gl-detail-export-pdf': { method: 'POST', path: '/v1/reports/gl-detail/export/pdf', summary: 'Export GL Detail report as PDF (one page per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
13
16
|
};
|
|
14
17
|
export default class Reports extends Command {
|
|
15
18
|
static description = 'Manage reports';
|
|
@@ -71,11 +74,6 @@ export default class Reports extends Command {
|
|
|
71
74
|
const endpoint = ENDPOINTS[args.action];
|
|
72
75
|
if (!endpoint)
|
|
73
76
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
74
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
75
|
-
if (!context)
|
|
76
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
77
|
-
if (!context.tokens)
|
|
78
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
79
77
|
let apiPath = endpoint.path;
|
|
80
78
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
81
79
|
const param = endpoint.pathParams[i];
|
|
@@ -94,6 +92,11 @@ export default class Reports extends Command {
|
|
|
94
92
|
}
|
|
95
93
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
96
94
|
}
|
|
95
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
96
|
+
if (!context)
|
|
97
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
98
|
+
if (!context.tokens)
|
|
99
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
97
100
|
const queryParams = {};
|
|
98
101
|
if (flags.page)
|
|
99
102
|
queryParams.page = flags.page;
|
|
@@ -120,15 +123,7 @@ export default class Reports extends Command {
|
|
|
120
123
|
if (eqIndex === -1)
|
|
121
124
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
122
125
|
const key = f.slice(0, eqIndex);
|
|
123
|
-
|
|
124
|
-
// Check for operator prefix (with or without $)
|
|
125
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
126
|
-
if (opMatch) {
|
|
127
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
value = `\$eq:${value}`;
|
|
131
|
-
}
|
|
126
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
132
127
|
const filterKey = `filter.${key}`;
|
|
133
128
|
if (filterKey in queryParams) {
|
|
134
129
|
// Multiple filters on same field: collect as array
|
|
@@ -161,7 +156,9 @@ export default class Reports extends Command {
|
|
|
161
156
|
data = form;
|
|
162
157
|
}
|
|
163
158
|
else if (endpoint.hasBody) {
|
|
164
|
-
|
|
159
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
160
|
+
// input file — skip the read-as-body branch.
|
|
161
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
165
162
|
const fsModule = await import('node:fs');
|
|
166
163
|
try {
|
|
167
164
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -183,6 +180,7 @@ export default class Reports extends Command {
|
|
|
183
180
|
if (!data)
|
|
184
181
|
data = {};
|
|
185
182
|
const body = data;
|
|
183
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
186
184
|
for (const pair of flags.set) {
|
|
187
185
|
const eqIndex = pair.indexOf('=');
|
|
188
186
|
if (eqIndex === -1)
|
|
@@ -194,6 +192,19 @@ export default class Reports extends Command {
|
|
|
194
192
|
value = JSON.parse(value);
|
|
195
193
|
}
|
|
196
194
|
catch { /* keep as string */ }
|
|
195
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
196
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
197
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
198
|
+
// already provided a JSON array.
|
|
199
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
200
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
201
|
+
if (typeof value === 'string') {
|
|
202
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
203
|
+
}
|
|
204
|
+
else if (value !== undefined && value !== null) {
|
|
205
|
+
value = [value];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
197
208
|
// Support nested keys like "address.city"
|
|
198
209
|
const keys = key.split('.');
|
|
199
210
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -244,6 +255,26 @@ export default class Reports extends Command {
|
|
|
244
255
|
process.stderr.write('\n');
|
|
245
256
|
}
|
|
246
257
|
try {
|
|
258
|
+
// Binary response: download to file
|
|
259
|
+
if (endpoint.isBinaryResponse) {
|
|
260
|
+
if (!flags.file) {
|
|
261
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
262
|
+
}
|
|
263
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
264
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
265
|
+
data,
|
|
266
|
+
contextName: flags.context,
|
|
267
|
+
binary: true,
|
|
268
|
+
});
|
|
269
|
+
const fsModule = await import('node:fs');
|
|
270
|
+
const binData = binResponse.data;
|
|
271
|
+
const buffer = Buffer.isBuffer(binData)
|
|
272
|
+
? binData
|
|
273
|
+
: Buffer.from(binData);
|
|
274
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
275
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
247
278
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
248
279
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
249
280
|
data,
|
|
@@ -334,6 +365,7 @@ export default class Reports extends Command {
|
|
|
334
365
|
}
|
|
335
366
|
else if (flags.set && flags.set.length > 0) {
|
|
336
367
|
const item = {};
|
|
368
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
337
369
|
for (const pair of flags.set) {
|
|
338
370
|
const eqIndex = pair.indexOf('=');
|
|
339
371
|
if (eqIndex === -1)
|
|
@@ -344,6 +376,15 @@ export default class Reports extends Command {
|
|
|
344
376
|
value = JSON.parse(value);
|
|
345
377
|
}
|
|
346
378
|
catch { /* keep as string */ }
|
|
379
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
380
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
381
|
+
if (typeof value === 'string') {
|
|
382
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
383
|
+
}
|
|
384
|
+
else if (value !== undefined && value !== null) {
|
|
385
|
+
value = [value];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
347
388
|
const keys = key.split('.');
|
|
348
389
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
349
390
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -493,14 +534,7 @@ export default class Reports extends Command {
|
|
|
493
534
|
if (eqIndex === -1)
|
|
494
535
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
495
536
|
const key = f.slice(0, eqIndex);
|
|
496
|
-
|
|
497
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
498
|
-
if (opMatch) {
|
|
499
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
value = `\$eq:${value}`;
|
|
503
|
-
}
|
|
537
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
504
538
|
const filterKey = `filter.${key}`;
|
|
505
539
|
if (filterKey in baseParams) {
|
|
506
540
|
const existing = baseParams[filterKey];
|
|
@@ -677,6 +711,7 @@ export default class Reports extends Command {
|
|
|
677
711
|
}
|
|
678
712
|
else if (flags.set && flags.set.length > 0) {
|
|
679
713
|
const item = {};
|
|
714
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
680
715
|
for (const pair of flags.set) {
|
|
681
716
|
const eqIndex = pair.indexOf('=');
|
|
682
717
|
if (eqIndex === -1)
|
|
@@ -687,6 +722,15 @@ export default class Reports extends Command {
|
|
|
687
722
|
value = JSON.parse(value);
|
|
688
723
|
}
|
|
689
724
|
catch { /* keep as string */ }
|
|
725
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
726
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
727
|
+
if (typeof value === 'string') {
|
|
728
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
729
|
+
}
|
|
730
|
+
else if (value !== undefined && value !== null) {
|
|
731
|
+
value = [value];
|
|
732
|
+
}
|
|
733
|
+
}
|
|
690
734
|
const keys = key.split('.');
|
|
691
735
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
692
736
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -806,6 +850,21 @@ export default class Reports extends Command {
|
|
|
806
850
|
this.log('');
|
|
807
851
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
808
852
|
}
|
|
853
|
+
parseFilterValue(value) {
|
|
854
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
855
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
856
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
857
|
+
// no operator prefix is present.
|
|
858
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
859
|
+
const m = value.match(opRe);
|
|
860
|
+
if (!m)
|
|
861
|
+
return `\$eq:${value}`;
|
|
862
|
+
const op = m[1];
|
|
863
|
+
const rest = value.slice(m[0].length);
|
|
864
|
+
if (rest === '')
|
|
865
|
+
return `\$${op}`;
|
|
866
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
867
|
+
}
|
|
809
868
|
filterFields(data, fields) {
|
|
810
869
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
811
870
|
const pick = (obj, keys) => {
|