@aderis-erp/cli 1.0.2 → 1.0.4
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 3.d.ts +40 -0
- package/dist/commands/{access-groups 2.js → access-groups 3.js } +106 -40
- package/dist/commands/access-groups.d.ts +40 -0
- package/dist/commands/access-groups.js +1074 -0
- package/dist/commands/account-transactions.d.ts +42 -0
- package/dist/commands/account-transactions.js +1088 -0
- package/dist/commands/accounts 2.d.ts +41 -0
- package/dist/commands/accounts 2.js +1078 -0
- package/dist/commands/accounts.d.ts +41 -0
- package/dist/commands/accounts.js +1080 -0
- package/dist/commands/agent-runs.d.ts +40 -0
- package/dist/commands/agent-runs.js +1071 -0
- package/dist/commands/async-endpoints-internal 3.d.ts +40 -0
- package/dist/commands/{processing-logs.js → async-endpoints-internal 3.js } +110 -46
- package/dist/commands/async-endpoints-internal.d.ts +40 -0
- package/dist/commands/async-endpoints-internal.js +1064 -0
- package/dist/commands/audit-log 3.d.ts +40 -0
- package/dist/commands/audit-log 3.js +1066 -0
- package/dist/commands/audit-log.d.ts +40 -0
- package/dist/commands/audit-log.js +1068 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.js +116 -0
- package/dist/commands/auth/logout.d.ts +10 -0
- package/dist/commands/auth/logout.js +58 -0
- package/dist/commands/auth/status.d.ts +12 -0
- package/dist/commands/auth/status.js +101 -0
- package/dist/commands/auth/tenant.d.ts +19 -0
- package/dist/commands/auth/tenant.js +184 -0
- package/dist/commands/auth.d.ts +37 -0
- package/dist/commands/auth.js +871 -0
- package/dist/commands/bank-accounts.d.ts +42 -0
- package/dist/commands/bank-accounts.js +1088 -0
- package/dist/commands/bank-integrations.d.ts +1 -0
- package/dist/commands/bank-integrations.js +122 -54
- package/dist/commands/business-partners 3.d.ts +41 -0
- package/dist/commands/business-partners 3.js +1078 -0
- package/dist/commands/business-partners---contacts 3.d.ts +42 -0
- package/dist/commands/business-partners---contacts 3.js +1072 -0
- package/dist/commands/business-partners---contacts.d.ts +1 -0
- package/dist/commands/business-partners---contacts.js +114 -46
- package/dist/commands/business-partners.d.ts +1 -0
- package/dist/commands/business-partners.js +114 -46
- package/dist/commands/config/add.d.ts +13 -0
- package/dist/commands/config/add.js +120 -0
- package/dist/commands/config/current.d.ts +9 -0
- package/dist/commands/config/current.js +43 -0
- package/dist/commands/config/delete.d.ts +12 -0
- package/dist/commands/config/delete.js +67 -0
- package/dist/commands/config/list.d.ts +9 -0
- package/dist/commands/config/list.js +50 -0
- package/dist/commands/config/use.d.ts +9 -0
- package/dist/commands/config/use.js +40 -0
- package/dist/commands/credit-card-accounts 3.d.ts +42 -0
- package/dist/commands/credit-card-accounts 3.js +1084 -0
- package/dist/commands/credit-card-accounts.d.ts +1 -0
- package/dist/commands/credit-card-accounts.js +119 -51
- package/dist/commands/documents.d.ts +1 -0
- package/dist/commands/documents.js +110 -42
- package/dist/commands/dunning-configs 3.d.ts +40 -0
- package/dist/commands/dunning-configs 3.js +1072 -0
- package/dist/commands/dunning-configs.d.ts +1 -0
- package/dist/commands/dunning-configs.js +109 -41
- package/dist/commands/dunning-reminders.d.ts +1 -0
- package/dist/commands/dunning-reminders.js +112 -44
- package/dist/commands/dunning-runs 3.d.ts +40 -0
- package/dist/commands/{pubsub.js → dunning-runs 3.js } +108 -43
- package/dist/commands/dunning-runs.d.ts +1 -0
- package/dist/commands/dunning-runs.js +105 -37
- package/dist/commands/employees.d.ts +1 -0
- package/dist/commands/employees.js +136 -68
- package/dist/commands/event-processor-mappings 3.d.ts +40 -0
- package/dist/commands/event-processor-mappings 3.js +1071 -0
- package/dist/commands/event-processor-mappings.d.ts +1 -0
- package/dist/commands/event-processor-mappings.js +109 -41
- package/dist/commands/files-internal 3.d.ts +40 -0
- package/dist/commands/files-internal 3.js +1073 -0
- package/dist/commands/files-internal.d.ts +1 -0
- package/dist/commands/files-internal.js +113 -45
- package/dist/commands/health 3.d.ts +40 -0
- package/dist/commands/health 3.js +1067 -0
- package/dist/commands/health.d.ts +1 -0
- package/dist/commands/health.js +109 -41
- package/dist/commands/{pubsub.d.ts → inbox 3.d.ts } +2 -1
- package/dist/commands/inbox 3.js +1065 -0
- package/dist/commands/inbox.d.ts +1 -0
- package/dist/commands/inbox.js +110 -40
- package/dist/commands/integrations 3.d.ts +41 -0
- package/dist/commands/integrations 3.js +1067 -0
- package/dist/commands/integrations.d.ts +1 -0
- package/dist/commands/integrations.js +107 -39
- package/dist/commands/item-categories 3.d.ts +40 -0
- package/dist/commands/item-categories 3.js +1072 -0
- package/dist/commands/item-categories.d.ts +1 -0
- package/dist/commands/item-categories.js +109 -41
- package/dist/commands/items 3.d.ts +40 -0
- package/dist/commands/items 3.js +1072 -0
- package/dist/commands/items.d.ts +1 -0
- package/dist/commands/items.js +109 -41
- package/dist/commands/journal-lines.d.ts +1 -0
- package/dist/commands/journal-lines.js +105 -37
- package/dist/commands/journals 3.d.ts +42 -0
- package/dist/commands/journals 3.js +1088 -0
- package/dist/commands/journals.d.ts +1 -0
- package/dist/commands/journals.js +123 -55
- package/dist/commands/ledgers.d.ts +1 -0
- package/dist/commands/ledgers.js +120 -52
- package/dist/commands/office-locations 3.d.ts +40 -0
- package/dist/commands/office-locations 3.js +1072 -0
- package/dist/commands/office-locations.d.ts +1 -0
- package/dist/commands/office-locations.js +109 -41
- package/dist/commands/payment-method-ledger-mappings 3.d.ts +41 -0
- package/dist/commands/payment-method-ledger-mappings 3.js +1066 -0
- package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
- package/dist/commands/payment-method-ledger-mappings.js +109 -41
- package/dist/commands/payment-methods 3.d.ts +40 -0
- package/dist/commands/payment-methods 3.js +1072 -0
- package/dist/commands/payment-methods.d.ts +1 -0
- package/dist/commands/payment-methods.js +109 -41
- package/dist/commands/payment-runs 3.d.ts +42 -0
- package/dist/commands/payment-runs 3.js +1087 -0
- package/dist/commands/payment-runs.d.ts +1 -0
- package/dist/commands/payment-runs.js +122 -54
- package/dist/commands/payment-terms 3.d.ts +40 -0
- package/dist/commands/payment-terms 3.js +1073 -0
- package/dist/commands/payment-terms.d.ts +1 -0
- package/dist/commands/payment-terms.js +110 -42
- package/dist/commands/price-lists 3.d.ts +41 -0
- package/dist/commands/price-lists 3.js +1078 -0
- package/dist/commands/price-lists.d.ts +1 -0
- package/dist/commands/price-lists.js +114 -46
- package/dist/commands/processor-mappings 2.d.ts +40 -0
- package/dist/commands/processor-mappings 2.js +1070 -0
- package/dist/commands/processor-mappings.d.ts +1 -0
- package/dist/commands/processor-mappings.js +108 -40
- package/dist/commands/processors.d.ts +1 -0
- package/dist/commands/processors.js +113 -45
- package/dist/commands/reconciliation 3.d.ts +40 -0
- package/dist/commands/reconciliation 3.js +1069 -0
- package/dist/commands/reconciliation.d.ts +1 -0
- package/dist/commands/reconciliation.js +110 -42
- package/dist/commands/reports.d.ts +1 -0
- package/dist/commands/reports.js +110 -39
- package/dist/commands/salary-types 3.d.ts +40 -0
- package/dist/commands/salary-types 3.js +1072 -0
- package/dist/commands/salary-types.d.ts +1 -0
- package/dist/commands/salary-types.js +1074 -0
- package/dist/commands/sales-invoices 3.d.ts +43 -0
- package/dist/commands/sales-invoices 3.js +1100 -0
- package/dist/commands/sales-invoices.d.ts +1 -0
- package/dist/commands/sales-invoices.js +134 -66
- package/dist/commands/sales-orders 3.d.ts +43 -0
- package/dist/commands/sales-orders 3.js +1098 -0
- package/dist/commands/sales-orders.d.ts +1 -0
- package/dist/commands/sales-orders.js +133 -64
- package/dist/commands/switch.d.ts +1 -0
- package/dist/commands/switch.js +58 -3
- package/dist/commands/{processing-logs.d.ts → tags 3.d.ts } +2 -1
- package/dist/commands/tags 3.js +1072 -0
- package/dist/commands/tags.d.ts +1 -0
- package/dist/commands/tags.js +109 -41
- package/dist/commands/tax-codes 3.d.ts +40 -0
- package/dist/commands/tax-codes 3.js +1075 -0
- package/dist/commands/tax-codes.d.ts +1 -0
- package/dist/commands/tax-codes.js +112 -44
- package/dist/commands/tax-matrix.d.ts +1 -0
- package/dist/commands/tax-matrix.js +109 -41
- package/dist/commands/tenants.d.ts +1 -0
- package/dist/commands/tenants.js +109 -41
- package/dist/commands/text-templates 3.d.ts +40 -0
- package/dist/commands/text-templates 3.js +1072 -0
- package/dist/commands/text-templates.d.ts +1 -0
- package/dist/commands/text-templates.js +109 -41
- package/dist/commands/tools.d.ts +1 -0
- package/dist/commands/tools.js +106 -38
- package/dist/commands/vendor-invoices.d.ts +1 -0
- package/dist/commands/vendor-invoices.js +139 -69
- package/dist/commands/webhooks 3.d.ts +40 -0
- package/dist/commands/webhooks 3.js +1074 -0
- package/dist/commands/webhooks.d.ts +1 -0
- package/dist/commands/webhooks.js +111 -43
- package/dist/commands/workflows.d.ts +1 -0
- package/dist/commands/workflows.js +107 -39
- package/dist/lib/api-client.d.ts +5 -1
- package/dist/lib/api-client.js +19 -5
- package/dist/lib/auth.d.ts +16 -1
- package/dist/lib/auth.js +170 -45
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +7 -1
- package/oclif.manifest.json +7464 -653
- package/package.json +2 -2
|
@@ -6,13 +6,12 @@ const HAS_IMPORT = false;
|
|
|
6
6
|
const HAS_EXPORT = false;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [...["
|
|
9
|
+
const SUBCOMMANDS = [...["trigger"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'
|
|
12
|
-
'processor-jobs': { method: 'POST', path: '/v1/pubsub/processor-jobs', summary: 'GCP Pub/Sub push endpoint for processor-job.v1 envelopes (PDF rendering)', pathParams: [], hasBody: false, isMultipart: false },
|
|
11
|
+
'trigger': { method: 'POST', path: '/v1/dunning-runs/trigger', summary: 'Manually trigger a dunning run', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
12
|
};
|
|
14
|
-
export default class
|
|
15
|
-
static description = 'Manage
|
|
13
|
+
export default class DunningRuns extends Command {
|
|
14
|
+
static description = 'Manage dunning runs';
|
|
16
15
|
static examples = [];
|
|
17
16
|
static args = {
|
|
18
17
|
action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
|
|
@@ -38,7 +37,7 @@ export default class Pubsub extends Command {
|
|
|
38
37
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
39
38
|
};
|
|
40
39
|
async run() {
|
|
41
|
-
const { args, flags } = await this.parse(
|
|
40
|
+
const { args, flags } = await this.parse(DunningRuns);
|
|
42
41
|
// Handle import action specially
|
|
43
42
|
if (args.action === 'import') {
|
|
44
43
|
if (!HAS_IMPORT)
|
|
@@ -84,7 +83,7 @@ export default class Pubsub extends Command {
|
|
|
84
83
|
// First path param always comes from positional argument
|
|
85
84
|
value = args.id;
|
|
86
85
|
if (!value)
|
|
87
|
-
this.error('Missing required argument: id. Usage: aderis
|
|
86
|
+
this.error('Missing required argument: id. Usage: aderis dunning-runs <action> <id>');
|
|
88
87
|
}
|
|
89
88
|
else {
|
|
90
89
|
const flagName = param.replace(/\./g, '-').replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
|
|
@@ -120,15 +119,7 @@ export default class Pubsub extends Command {
|
|
|
120
119
|
if (eqIndex === -1)
|
|
121
120
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
122
121
|
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
|
-
}
|
|
122
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
132
123
|
const filterKey = `filter.${key}`;
|
|
133
124
|
if (filterKey in queryParams) {
|
|
134
125
|
// Multiple filters on same field: collect as array
|
|
@@ -183,6 +174,7 @@ export default class Pubsub extends Command {
|
|
|
183
174
|
if (!data)
|
|
184
175
|
data = {};
|
|
185
176
|
const body = data;
|
|
177
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
186
178
|
for (const pair of flags.set) {
|
|
187
179
|
const eqIndex = pair.indexOf('=');
|
|
188
180
|
if (eqIndex === -1)
|
|
@@ -194,6 +186,19 @@ export default class Pubsub extends Command {
|
|
|
194
186
|
value = JSON.parse(value);
|
|
195
187
|
}
|
|
196
188
|
catch { /* keep as string */ }
|
|
189
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
190
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
191
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
192
|
+
// already provided a JSON array.
|
|
193
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
194
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
195
|
+
if (typeof value === 'string') {
|
|
196
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
197
|
+
}
|
|
198
|
+
else if (value !== undefined && value !== null) {
|
|
199
|
+
value = [value];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
197
202
|
// Support nested keys like "address.city"
|
|
198
203
|
const keys = key.split('.');
|
|
199
204
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -244,6 +249,26 @@ export default class Pubsub extends Command {
|
|
|
244
249
|
process.stderr.write('\n');
|
|
245
250
|
}
|
|
246
251
|
try {
|
|
252
|
+
// Binary response: download to file
|
|
253
|
+
if (endpoint.isBinaryResponse) {
|
|
254
|
+
if (!flags.file) {
|
|
255
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
256
|
+
}
|
|
257
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
258
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
259
|
+
data,
|
|
260
|
+
contextName: flags.context,
|
|
261
|
+
binary: true,
|
|
262
|
+
});
|
|
263
|
+
const fsModule = await import('node:fs');
|
|
264
|
+
const binData = binResponse.data;
|
|
265
|
+
const buffer = Buffer.isBuffer(binData)
|
|
266
|
+
? binData
|
|
267
|
+
: Buffer.from(binData);
|
|
268
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
269
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
247
272
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
248
273
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
249
274
|
data,
|
|
@@ -334,6 +359,7 @@ export default class Pubsub extends Command {
|
|
|
334
359
|
}
|
|
335
360
|
else if (flags.set && flags.set.length > 0) {
|
|
336
361
|
const item = {};
|
|
362
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
337
363
|
for (const pair of flags.set) {
|
|
338
364
|
const eqIndex = pair.indexOf('=');
|
|
339
365
|
if (eqIndex === -1)
|
|
@@ -344,6 +370,15 @@ export default class Pubsub extends Command {
|
|
|
344
370
|
value = JSON.parse(value);
|
|
345
371
|
}
|
|
346
372
|
catch { /* keep as string */ }
|
|
373
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
374
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
375
|
+
if (typeof value === 'string') {
|
|
376
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
377
|
+
}
|
|
378
|
+
else if (value !== undefined && value !== null) {
|
|
379
|
+
value = [value];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
347
382
|
const keys = key.split('.');
|
|
348
383
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
349
384
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -493,14 +528,7 @@ export default class Pubsub extends Command {
|
|
|
493
528
|
if (eqIndex === -1)
|
|
494
529
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
495
530
|
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
|
-
}
|
|
531
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
504
532
|
const filterKey = `filter.${key}`;
|
|
505
533
|
if (filterKey in baseParams) {
|
|
506
534
|
const existing = baseParams[filterKey];
|
|
@@ -647,7 +675,7 @@ export default class Pubsub extends Command {
|
|
|
647
675
|
}
|
|
648
676
|
async runNestedImport(suffix, parentId, flags) {
|
|
649
677
|
if (!parentId) {
|
|
650
|
-
this.error(`Missing required argument: id. Usage: aderis
|
|
678
|
+
this.error(`Missing required argument: id. Usage: aderis dunning-runs import-${suffix} <parent-id> --file data.json`);
|
|
651
679
|
}
|
|
652
680
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
653
681
|
if (!context)
|
|
@@ -677,6 +705,7 @@ export default class Pubsub extends Command {
|
|
|
677
705
|
}
|
|
678
706
|
else if (flags.set && flags.set.length > 0) {
|
|
679
707
|
const item = {};
|
|
708
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
680
709
|
for (const pair of flags.set) {
|
|
681
710
|
const eqIndex = pair.indexOf('=');
|
|
682
711
|
if (eqIndex === -1)
|
|
@@ -687,6 +716,15 @@ export default class Pubsub extends Command {
|
|
|
687
716
|
value = JSON.parse(value);
|
|
688
717
|
}
|
|
689
718
|
catch { /* keep as string */ }
|
|
719
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
720
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
721
|
+
if (typeof value === 'string') {
|
|
722
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
723
|
+
}
|
|
724
|
+
else if (value !== undefined && value !== null) {
|
|
725
|
+
value = [value];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
690
728
|
const keys = key.split('.');
|
|
691
729
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
692
730
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -806,35 +844,62 @@ export default class Pubsub extends Command {
|
|
|
806
844
|
this.log('');
|
|
807
845
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
808
846
|
}
|
|
847
|
+
parseFilterValue(value) {
|
|
848
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
849
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
850
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
851
|
+
// no operator prefix is present.
|
|
852
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
853
|
+
const m = value.match(opRe);
|
|
854
|
+
if (!m)
|
|
855
|
+
return `\$eq:${value}`;
|
|
856
|
+
const op = m[1];
|
|
857
|
+
const rest = value.slice(m[0].length);
|
|
858
|
+
if (rest === '')
|
|
859
|
+
return `\$${op}`;
|
|
860
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
861
|
+
}
|
|
809
862
|
filterFields(data, fields) {
|
|
810
863
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
811
864
|
const pick = (obj, keys) => {
|
|
812
|
-
|
|
865
|
+
// Group keys by their first segment so sibling sub-paths
|
|
866
|
+
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
867
|
+
// and don't clobber each other on merge.
|
|
868
|
+
const directKeys = new Set();
|
|
869
|
+
const nestedKeys = new Map();
|
|
813
870
|
for (const key of keys) {
|
|
814
871
|
if (key.split('.').some(isUnsafeKey))
|
|
815
872
|
continue;
|
|
816
873
|
if (key.includes('.')) {
|
|
817
|
-
// Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
|
|
818
874
|
const [first, ...rest] = key.split('.');
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
...pick(item, [restKey]),
|
|
828
|
-
}));
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
831
|
-
const nested = pick(val, [restKey]);
|
|
832
|
-
result[first] = { ...(result[first] || {}), ...nested };
|
|
833
|
-
}
|
|
875
|
+
if (isUnsafeKey(first))
|
|
876
|
+
continue;
|
|
877
|
+
const list = nestedKeys.get(first);
|
|
878
|
+
if (list) {
|
|
879
|
+
list.push(rest.join('.'));
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
nestedKeys.set(first, [rest.join('.')]);
|
|
834
883
|
}
|
|
835
884
|
}
|
|
836
|
-
else
|
|
885
|
+
else {
|
|
886
|
+
directKeys.add(key);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const result = {};
|
|
890
|
+
for (const key of directKeys) {
|
|
891
|
+
if (key in obj)
|
|
837
892
|
result[key] = obj[key];
|
|
893
|
+
}
|
|
894
|
+
for (const [first, restKeys] of nestedKeys) {
|
|
895
|
+
const val = obj[first];
|
|
896
|
+
if (!val || typeof val !== 'object')
|
|
897
|
+
continue;
|
|
898
|
+
if (Array.isArray(val)) {
|
|
899
|
+
result[first] = val.map(item => pick(item, restKeys));
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
result[first] = pick(val, restKeys);
|
|
838
903
|
}
|
|
839
904
|
}
|
|
840
905
|
return result;
|
|
@@ -8,7 +8,7 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["trigger"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'trigger': { method: 'POST', path: '/v1/dunning-runs/trigger', summary: 'Manually trigger a dunning run', pathParams: [], hasBody: true, isMultipart: false },
|
|
11
|
+
'trigger': { method: 'POST', path: '/v1/dunning-runs/trigger', summary: 'Manually trigger a dunning run', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
12
|
};
|
|
13
13
|
export default class DunningRuns extends Command {
|
|
14
14
|
static description = 'Manage dunning runs';
|
|
@@ -119,15 +119,7 @@ export default class DunningRuns extends Command {
|
|
|
119
119
|
if (eqIndex === -1)
|
|
120
120
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
121
121
|
const key = f.slice(0, eqIndex);
|
|
122
|
-
|
|
123
|
-
// Check for operator prefix (with or without $)
|
|
124
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
125
|
-
if (opMatch) {
|
|
126
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
value = `\$eq:${value}`;
|
|
130
|
-
}
|
|
122
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
131
123
|
const filterKey = `filter.${key}`;
|
|
132
124
|
if (filterKey in queryParams) {
|
|
133
125
|
// Multiple filters on same field: collect as array
|
|
@@ -160,7 +152,9 @@ export default class DunningRuns extends Command {
|
|
|
160
152
|
data = form;
|
|
161
153
|
}
|
|
162
154
|
else if (endpoint.hasBody) {
|
|
163
|
-
|
|
155
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
156
|
+
// input file — skip the read-as-body branch.
|
|
157
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
164
158
|
const fsModule = await import('node:fs');
|
|
165
159
|
try {
|
|
166
160
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -182,6 +176,7 @@ export default class DunningRuns extends Command {
|
|
|
182
176
|
if (!data)
|
|
183
177
|
data = {};
|
|
184
178
|
const body = data;
|
|
179
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
185
180
|
for (const pair of flags.set) {
|
|
186
181
|
const eqIndex = pair.indexOf('=');
|
|
187
182
|
if (eqIndex === -1)
|
|
@@ -193,6 +188,19 @@ export default class DunningRuns extends Command {
|
|
|
193
188
|
value = JSON.parse(value);
|
|
194
189
|
}
|
|
195
190
|
catch { /* keep as string */ }
|
|
191
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
192
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
193
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
194
|
+
// already provided a JSON array.
|
|
195
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
196
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
197
|
+
if (typeof value === 'string') {
|
|
198
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
199
|
+
}
|
|
200
|
+
else if (value !== undefined && value !== null) {
|
|
201
|
+
value = [value];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
196
204
|
// Support nested keys like "address.city"
|
|
197
205
|
const keys = key.split('.');
|
|
198
206
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -243,6 +251,26 @@ export default class DunningRuns extends Command {
|
|
|
243
251
|
process.stderr.write('\n');
|
|
244
252
|
}
|
|
245
253
|
try {
|
|
254
|
+
// Binary response: download to file
|
|
255
|
+
if (endpoint.isBinaryResponse) {
|
|
256
|
+
if (!flags.file) {
|
|
257
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
258
|
+
}
|
|
259
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
260
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
261
|
+
data,
|
|
262
|
+
contextName: flags.context,
|
|
263
|
+
binary: true,
|
|
264
|
+
});
|
|
265
|
+
const fsModule = await import('node:fs');
|
|
266
|
+
const binData = binResponse.data;
|
|
267
|
+
const buffer = Buffer.isBuffer(binData)
|
|
268
|
+
? binData
|
|
269
|
+
: Buffer.from(binData);
|
|
270
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
271
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
246
274
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
247
275
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
248
276
|
data,
|
|
@@ -333,6 +361,7 @@ export default class DunningRuns extends Command {
|
|
|
333
361
|
}
|
|
334
362
|
else if (flags.set && flags.set.length > 0) {
|
|
335
363
|
const item = {};
|
|
364
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
336
365
|
for (const pair of flags.set) {
|
|
337
366
|
const eqIndex = pair.indexOf('=');
|
|
338
367
|
if (eqIndex === -1)
|
|
@@ -343,6 +372,15 @@ export default class DunningRuns extends Command {
|
|
|
343
372
|
value = JSON.parse(value);
|
|
344
373
|
}
|
|
345
374
|
catch { /* keep as string */ }
|
|
375
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
376
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
377
|
+
if (typeof value === 'string') {
|
|
378
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
379
|
+
}
|
|
380
|
+
else if (value !== undefined && value !== null) {
|
|
381
|
+
value = [value];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
346
384
|
const keys = key.split('.');
|
|
347
385
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
348
386
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -492,14 +530,7 @@ export default class DunningRuns extends Command {
|
|
|
492
530
|
if (eqIndex === -1)
|
|
493
531
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
494
532
|
const key = f.slice(0, eqIndex);
|
|
495
|
-
|
|
496
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
497
|
-
if (opMatch) {
|
|
498
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
value = `\$eq:${value}`;
|
|
502
|
-
}
|
|
533
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
503
534
|
const filterKey = `filter.${key}`;
|
|
504
535
|
if (filterKey in baseParams) {
|
|
505
536
|
const existing = baseParams[filterKey];
|
|
@@ -676,6 +707,7 @@ export default class DunningRuns extends Command {
|
|
|
676
707
|
}
|
|
677
708
|
else if (flags.set && flags.set.length > 0) {
|
|
678
709
|
const item = {};
|
|
710
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
679
711
|
for (const pair of flags.set) {
|
|
680
712
|
const eqIndex = pair.indexOf('=');
|
|
681
713
|
if (eqIndex === -1)
|
|
@@ -686,6 +718,15 @@ export default class DunningRuns extends Command {
|
|
|
686
718
|
value = JSON.parse(value);
|
|
687
719
|
}
|
|
688
720
|
catch { /* keep as string */ }
|
|
721
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
722
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
723
|
+
if (typeof value === 'string') {
|
|
724
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
725
|
+
}
|
|
726
|
+
else if (value !== undefined && value !== null) {
|
|
727
|
+
value = [value];
|
|
728
|
+
}
|
|
729
|
+
}
|
|
689
730
|
const keys = key.split('.');
|
|
690
731
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
691
732
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -805,35 +846,62 @@ export default class DunningRuns extends Command {
|
|
|
805
846
|
this.log('');
|
|
806
847
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
807
848
|
}
|
|
849
|
+
parseFilterValue(value) {
|
|
850
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
851
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
852
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
853
|
+
// no operator prefix is present.
|
|
854
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
855
|
+
const m = value.match(opRe);
|
|
856
|
+
if (!m)
|
|
857
|
+
return `\$eq:${value}`;
|
|
858
|
+
const op = m[1];
|
|
859
|
+
const rest = value.slice(m[0].length);
|
|
860
|
+
if (rest === '')
|
|
861
|
+
return `\$${op}`;
|
|
862
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
863
|
+
}
|
|
808
864
|
filterFields(data, fields) {
|
|
809
865
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
810
866
|
const pick = (obj, keys) => {
|
|
811
|
-
|
|
867
|
+
// Group keys by their first segment so sibling sub-paths
|
|
868
|
+
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
869
|
+
// and don't clobber each other on merge.
|
|
870
|
+
const directKeys = new Set();
|
|
871
|
+
const nestedKeys = new Map();
|
|
812
872
|
for (const key of keys) {
|
|
813
873
|
if (key.split('.').some(isUnsafeKey))
|
|
814
874
|
continue;
|
|
815
875
|
if (key.includes('.')) {
|
|
816
|
-
// Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
|
|
817
876
|
const [first, ...rest] = key.split('.');
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
...pick(item, [restKey]),
|
|
827
|
-
}));
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
const nested = pick(val, [restKey]);
|
|
831
|
-
result[first] = { ...(result[first] || {}), ...nested };
|
|
832
|
-
}
|
|
877
|
+
if (isUnsafeKey(first))
|
|
878
|
+
continue;
|
|
879
|
+
const list = nestedKeys.get(first);
|
|
880
|
+
if (list) {
|
|
881
|
+
list.push(rest.join('.'));
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
nestedKeys.set(first, [rest.join('.')]);
|
|
833
885
|
}
|
|
834
886
|
}
|
|
835
|
-
else
|
|
887
|
+
else {
|
|
888
|
+
directKeys.add(key);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const result = {};
|
|
892
|
+
for (const key of directKeys) {
|
|
893
|
+
if (key in obj)
|
|
836
894
|
result[key] = obj[key];
|
|
895
|
+
}
|
|
896
|
+
for (const [first, restKeys] of nestedKeys) {
|
|
897
|
+
const val = obj[first];
|
|
898
|
+
if (!val || typeof val !== 'object')
|
|
899
|
+
continue;
|
|
900
|
+
if (Array.isArray(val)) {
|
|
901
|
+
result[first] = val.map(item => pick(item, restKeys));
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
result[first] = pick(val, restKeys);
|
|
837
905
|
}
|
|
838
906
|
}
|
|
839
907
|
return result;
|