@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.
Files changed (111) hide show
  1. package/README.md +6 -0
  2. package/dist/commands/access-groups.d.ts +1 -0
  3. package/dist/commands/access-groups.js +84 -28
  4. package/dist/commands/{async-endpoints-internal.d.ts → account-transaction-allocations.d.ts} +3 -2
  5. package/dist/commands/{async-endpoints-internal.js → account-transaction-allocations.js} +93 -33
  6. package/dist/commands/account-transactions.d.ts +1 -0
  7. package/dist/commands/account-transactions.js +96 -40
  8. package/dist/commands/accounts.d.ts +1 -0
  9. package/dist/commands/accounts.js +89 -33
  10. package/dist/commands/agent-runs.d.ts +1 -0
  11. package/dist/commands/agent-runs.js +85 -27
  12. package/dist/commands/audit-log.d.ts +1 -0
  13. package/dist/commands/audit-log.js +82 -26
  14. package/dist/commands/auth/login.js +1 -2
  15. package/dist/commands/auth/tenant.d.ts +1 -1
  16. package/dist/commands/auth/tenant.js +11 -11
  17. package/dist/commands/auth.js +1 -1
  18. package/dist/commands/bank-accounts.d.ts +1 -0
  19. package/dist/commands/bank-accounts.js +96 -40
  20. package/dist/commands/bank-integrations.d.ts +1 -0
  21. package/dist/commands/bank-integrations.js +97 -41
  22. package/dist/commands/business-partners---contacts.d.ts +1 -0
  23. package/dist/commands/business-partners---contacts.js +89 -33
  24. package/dist/commands/business-partners.d.ts +1 -0
  25. package/dist/commands/business-partners.js +89 -33
  26. package/dist/commands/credit-card-accounts.d.ts +1 -0
  27. package/dist/commands/credit-card-accounts.js +94 -38
  28. package/dist/commands/documents.d.ts +1 -0
  29. package/dist/commands/documents.js +85 -29
  30. package/dist/commands/dunning-configs.d.ts +1 -0
  31. package/dist/commands/dunning-configs.js +84 -28
  32. package/dist/commands/dunning-reminders.d.ts +1 -0
  33. package/dist/commands/dunning-reminders.js +87 -31
  34. package/dist/commands/dunning-runs.d.ts +1 -0
  35. package/dist/commands/dunning-runs.js +80 -24
  36. package/dist/commands/employees.d.ts +1 -0
  37. package/dist/commands/employees.js +111 -55
  38. package/dist/commands/event-processor-mappings.d.ts +1 -0
  39. package/dist/commands/event-processor-mappings.js +84 -28
  40. package/dist/commands/files-internal.d.ts +1 -0
  41. package/dist/commands/files-internal.js +88 -32
  42. package/dist/commands/health.d.ts +1 -0
  43. package/dist/commands/health.js +84 -28
  44. package/dist/commands/inbox.d.ts +1 -0
  45. package/dist/commands/inbox.js +84 -28
  46. package/dist/commands/integrations.d.ts +1 -0
  47. package/dist/commands/integrations.js +82 -26
  48. package/dist/commands/item-categories.d.ts +1 -0
  49. package/dist/commands/item-categories.js +84 -28
  50. package/dist/commands/items.d.ts +1 -0
  51. package/dist/commands/items.js +84 -28
  52. package/dist/commands/journal-lines.d.ts +1 -0
  53. package/dist/commands/journal-lines.js +80 -24
  54. package/dist/commands/journals.d.ts +1 -0
  55. package/dist/commands/journals.js +98 -42
  56. package/dist/commands/ledgers.d.ts +1 -0
  57. package/dist/commands/ledgers.js +95 -39
  58. package/dist/commands/office-locations.d.ts +1 -0
  59. package/dist/commands/office-locations.js +84 -28
  60. package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
  61. package/dist/commands/payment-method-ledger-mappings.js +84 -28
  62. package/dist/commands/payment-methods.d.ts +1 -0
  63. package/dist/commands/payment-methods.js +84 -28
  64. package/dist/commands/payment-runs.d.ts +1 -0
  65. package/dist/commands/payment-runs.js +97 -41
  66. package/dist/commands/payment-terms.d.ts +1 -0
  67. package/dist/commands/payment-terms.js +85 -29
  68. package/dist/commands/price-lists.d.ts +1 -0
  69. package/dist/commands/price-lists.js +89 -33
  70. package/dist/commands/processor-mappings.d.ts +1 -0
  71. package/dist/commands/processor-mappings.js +83 -27
  72. package/dist/commands/processors.d.ts +1 -0
  73. package/dist/commands/processors.js +88 -32
  74. package/dist/commands/reconciliation.d.ts +1 -0
  75. package/dist/commands/reconciliation.js +85 -29
  76. package/dist/commands/reports.d.ts +1 -0
  77. package/dist/commands/reports.js +85 -26
  78. package/dist/commands/salary-types.d.ts +1 -0
  79. package/dist/commands/salary-types.js +84 -28
  80. package/dist/commands/sales-invoices.d.ts +1 -0
  81. package/dist/commands/sales-invoices.js +109 -53
  82. package/dist/commands/sales-orders.d.ts +1 -0
  83. package/dist/commands/sales-orders.js +107 -51
  84. package/dist/commands/tags.d.ts +1 -0
  85. package/dist/commands/tags.js +84 -28
  86. package/dist/commands/tax-codes.d.ts +1 -0
  87. package/dist/commands/tax-codes.js +87 -31
  88. package/dist/commands/tax-matrix.d.ts +1 -0
  89. package/dist/commands/tax-matrix.js +84 -28
  90. package/dist/commands/tenants.d.ts +1 -0
  91. package/dist/commands/tenants.js +84 -28
  92. package/dist/commands/text-templates.d.ts +1 -0
  93. package/dist/commands/text-templates.js +84 -28
  94. package/dist/commands/tools.d.ts +1 -0
  95. package/dist/commands/tools.js +81 -25
  96. package/dist/commands/vat-declaration.d.ts +41 -0
  97. package/dist/commands/vat-declaration.js +1079 -0
  98. package/dist/commands/vendor-invoices.d.ts +1 -0
  99. package/dist/commands/vendor-invoices.js +113 -57
  100. package/dist/commands/webhooks.d.ts +1 -0
  101. package/dist/commands/webhooks.js +86 -30
  102. package/dist/commands/workflows.d.ts +1 -0
  103. package/dist/commands/workflows.js +82 -26
  104. package/dist/lib/api-client.d.ts +6 -1
  105. package/dist/lib/api-client.js +23 -5
  106. package/dist/lib/auth.js +18 -0
  107. package/dist/lib/config.js +1 -1
  108. package/oclif.manifest.json +229 -52
  109. package/package.json +15 -13
  110. package/dist/index 2.js +0 -1
  111. package/dist/index.d 2.ts +0 -1
@@ -8,21 +8,21 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["ledger-mappings", "notes"];
9
9
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
14
- 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false },
15
- 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false },
16
- 'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false },
17
- 'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false },
18
- 'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false },
19
- 'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false },
20
- 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false },
21
- 'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false },
22
- 'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false },
23
- 'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false },
24
- 'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false },
25
- 'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['last4', 'notes'] },
13
+ 'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['last4', 'notes'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
+ 'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
+ 'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
+ 'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
+ 'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
+ 'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
+ 'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
+ 'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
+ 'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
+ 'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
26
  };
27
27
  export default class CreditCardAccounts extends Command {
28
28
  static description = 'Manage credit card accounts';
@@ -93,11 +93,6 @@ export default class CreditCardAccounts extends Command {
93
93
  const endpoint = ENDPOINTS[args.action];
94
94
  if (!endpoint)
95
95
  this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
96
- const context = flags.context ? getContext(flags.context) : getCurrentContext();
97
- if (!context)
98
- this.error('No context configured. Run: aderis auth login --new');
99
- if (!context.tokens)
100
- this.error('Not logged in. Run: aderis auth login');
101
96
  let apiPath = endpoint.path;
102
97
  for (let i = 0; i < endpoint.pathParams.length; i++) {
103
98
  const param = endpoint.pathParams[i];
@@ -116,6 +111,11 @@ export default class CreditCardAccounts extends Command {
116
111
  }
117
112
  apiPath = apiPath.replace(`{${param}}`, String(value));
118
113
  }
114
+ const context = flags.context ? getContext(flags.context) : getCurrentContext();
115
+ if (!context)
116
+ this.error('No context configured. Run: aderis auth login --new');
117
+ if (!context.tokens)
118
+ this.error('Not logged in. Run: aderis auth login');
119
119
  const queryParams = {};
120
120
  if (flags.page)
121
121
  queryParams.page = flags.page;
@@ -142,15 +142,7 @@ export default class CreditCardAccounts extends Command {
142
142
  if (eqIndex === -1)
143
143
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
144
144
  const key = f.slice(0, eqIndex);
145
- let value = f.slice(eqIndex + 1);
146
- // Check for operator prefix (with or without $)
147
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
148
- if (opMatch) {
149
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
150
- }
151
- else {
152
- value = `\$eq:${value}`;
153
- }
145
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
154
146
  const filterKey = `filter.${key}`;
155
147
  if (filterKey in queryParams) {
156
148
  // Multiple filters on same field: collect as array
@@ -183,7 +175,9 @@ export default class CreditCardAccounts extends Command {
183
175
  data = form;
184
176
  }
185
177
  else if (endpoint.hasBody) {
186
- if (flags.file) {
178
+ // For binary-response endpoints, --file is the output path, not a JSON
179
+ // input file — skip the read-as-body branch.
180
+ if (flags.file && !endpoint.isBinaryResponse) {
187
181
  const fsModule = await import('node:fs');
188
182
  try {
189
183
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -205,6 +199,7 @@ export default class CreditCardAccounts extends Command {
205
199
  if (!data)
206
200
  data = {};
207
201
  const body = data;
202
+ const arrayFields = new Set(endpoint.bodyArrayFields);
208
203
  for (const pair of flags.set) {
209
204
  const eqIndex = pair.indexOf('=');
210
205
  if (eqIndex === -1)
@@ -216,6 +211,19 @@ export default class CreditCardAccounts extends Command {
216
211
  value = JSON.parse(value);
217
212
  }
218
213
  catch { /* keep as string */ }
214
+ // For top-level keys typed as array in the API schema, split bare
215
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
216
+ // wrap a lone scalar in a single-element array. Skips when --set
217
+ // already provided a JSON array.
218
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
219
+ if (isArrayField && !Array.isArray(value)) {
220
+ if (typeof value === 'string') {
221
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
222
+ }
223
+ else if (value !== undefined && value !== null) {
224
+ value = [value];
225
+ }
226
+ }
219
227
  // Support nested keys like "address.city"
220
228
  const keys = key.split('.');
221
229
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -266,6 +274,26 @@ export default class CreditCardAccounts extends Command {
266
274
  process.stderr.write('\n');
267
275
  }
268
276
  try {
277
+ // Binary response: download to file
278
+ if (endpoint.isBinaryResponse) {
279
+ if (!flags.file) {
280
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
281
+ }
282
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
283
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
284
+ data,
285
+ contextName: flags.context,
286
+ binary: true,
287
+ });
288
+ const fsModule = await import('node:fs');
289
+ const binData = binResponse.data;
290
+ const buffer = Buffer.isBuffer(binData)
291
+ ? binData
292
+ : Buffer.from(binData);
293
+ fsModule.writeFileSync(flags.file, buffer);
294
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
295
+ return;
296
+ }
269
297
  const response = await apiRequest(endpoint.method, apiPath, {
270
298
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
271
299
  data,
@@ -356,6 +384,7 @@ export default class CreditCardAccounts extends Command {
356
384
  }
357
385
  else if (flags.set && flags.set.length > 0) {
358
386
  const item = {};
387
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
359
388
  for (const pair of flags.set) {
360
389
  const eqIndex = pair.indexOf('=');
361
390
  if (eqIndex === -1)
@@ -366,6 +395,15 @@ export default class CreditCardAccounts extends Command {
366
395
  value = JSON.parse(value);
367
396
  }
368
397
  catch { /* keep as string */ }
398
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
399
+ if (isArrayField && !Array.isArray(value)) {
400
+ if (typeof value === 'string') {
401
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
402
+ }
403
+ else if (value !== undefined && value !== null) {
404
+ value = [value];
405
+ }
406
+ }
369
407
  const keys = key.split('.');
370
408
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
371
409
  this.error(`Invalid --set key: ${key}`);
@@ -515,14 +553,7 @@ export default class CreditCardAccounts extends Command {
515
553
  if (eqIndex === -1)
516
554
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
517
555
  const key = f.slice(0, eqIndex);
518
- let value = f.slice(eqIndex + 1);
519
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
520
- if (opMatch) {
521
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
522
- }
523
- else {
524
- value = `\$eq:${value}`;
525
- }
556
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
526
557
  const filterKey = `filter.${key}`;
527
558
  if (filterKey in baseParams) {
528
559
  const existing = baseParams[filterKey];
@@ -699,6 +730,7 @@ export default class CreditCardAccounts extends Command {
699
730
  }
700
731
  else if (flags.set && flags.set.length > 0) {
701
732
  const item = {};
733
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
702
734
  for (const pair of flags.set) {
703
735
  const eqIndex = pair.indexOf('=');
704
736
  if (eqIndex === -1)
@@ -709,6 +741,15 @@ export default class CreditCardAccounts extends Command {
709
741
  value = JSON.parse(value);
710
742
  }
711
743
  catch { /* keep as string */ }
744
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
745
+ if (isArrayField && !Array.isArray(value)) {
746
+ if (typeof value === 'string') {
747
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
748
+ }
749
+ else if (value !== undefined && value !== null) {
750
+ value = [value];
751
+ }
752
+ }
712
753
  const keys = key.split('.');
713
754
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
714
755
  this.error(`Invalid --set key: ${key}`);
@@ -828,6 +869,21 @@ export default class CreditCardAccounts extends Command {
828
869
  this.log('');
829
870
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
830
871
  }
872
+ parseFilterValue(value) {
873
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
874
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
875
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
876
+ // no operator prefix is present.
877
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
878
+ const m = value.match(opRe);
879
+ if (!m)
880
+ return `\$eq:${value}`;
881
+ const op = m[1];
882
+ const rest = value.slice(m[0].length);
883
+ if (rest === '')
884
+ return `\$${op}`;
885
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
886
+ }
831
887
  filterFields(data, fields) {
832
888
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
833
889
  const pick = (obj, keys) => {
@@ -30,6 +30,7 @@ export default class Documents extends Command {
30
30
  private runExport;
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
+ private parseFilterValue;
33
34
  private filterFields;
34
35
  private sanitizeOutput;
35
36
  private getColumns;
@@ -8,12 +8,12 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/documents', summary: 'Create a new document (letterbox)', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false },
14
- 'update': { method: 'PATCH', path: '/v1/documents/{id}', summary: 'Patch an existing document', pathParams: ['id'], hasBody: true, isMultipart: false },
15
- 'delete': { method: 'DELETE', path: '/v1/documents/{id}', summary: 'Soft-delete (discard) a document', pathParams: ['id'], hasBody: true, isMultipart: false },
16
- 'reclassify': { method: 'POST', path: '/v1/documents/{id}/reclassify', summary: 'Reclassify a document to a new type', pathParams: ['id'], hasBody: true, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/documents', summary: 'List documents (paginated)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/documents', summary: 'Create a new document (letterbox)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['references', 'tags', 'files'] },
13
+ 'get': { method: 'GET', path: '/v1/documents/{id}', summary: 'Get document detail with files, audit log, and notes placeholder', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/documents/{id}', summary: 'Patch an existing document', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['references', 'tags', 'files'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/documents/{id}', summary: 'Soft-delete (discard) a document', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'reclassify': { method: 'POST', path: '/v1/documents/{id}/reclassify', summary: 'Reclassify a document to a new type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
17
  };
18
18
  export default class Documents extends Command {
19
19
  static description = 'Manage documents';
@@ -82,11 +82,6 @@ export default class Documents extends Command {
82
82
  const endpoint = ENDPOINTS[args.action];
83
83
  if (!endpoint)
84
84
  this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
85
- const context = flags.context ? getContext(flags.context) : getCurrentContext();
86
- if (!context)
87
- this.error('No context configured. Run: aderis auth login --new');
88
- if (!context.tokens)
89
- this.error('Not logged in. Run: aderis auth login');
90
85
  let apiPath = endpoint.path;
91
86
  for (let i = 0; i < endpoint.pathParams.length; i++) {
92
87
  const param = endpoint.pathParams[i];
@@ -105,6 +100,11 @@ export default class Documents extends Command {
105
100
  }
106
101
  apiPath = apiPath.replace(`{${param}}`, String(value));
107
102
  }
103
+ const context = flags.context ? getContext(flags.context) : getCurrentContext();
104
+ if (!context)
105
+ this.error('No context configured. Run: aderis auth login --new');
106
+ if (!context.tokens)
107
+ this.error('Not logged in. Run: aderis auth login');
108
108
  const queryParams = {};
109
109
  if (flags.page)
110
110
  queryParams.page = flags.page;
@@ -131,15 +131,7 @@ export default class Documents extends Command {
131
131
  if (eqIndex === -1)
132
132
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
133
133
  const key = f.slice(0, eqIndex);
134
- let value = f.slice(eqIndex + 1);
135
- // Check for operator prefix (with or without $)
136
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
137
- if (opMatch) {
138
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
139
- }
140
- else {
141
- value = `\$eq:${value}`;
142
- }
134
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
143
135
  const filterKey = `filter.${key}`;
144
136
  if (filterKey in queryParams) {
145
137
  // Multiple filters on same field: collect as array
@@ -172,7 +164,9 @@ export default class Documents extends Command {
172
164
  data = form;
173
165
  }
174
166
  else if (endpoint.hasBody) {
175
- if (flags.file) {
167
+ // For binary-response endpoints, --file is the output path, not a JSON
168
+ // input file — skip the read-as-body branch.
169
+ if (flags.file && !endpoint.isBinaryResponse) {
176
170
  const fsModule = await import('node:fs');
177
171
  try {
178
172
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -194,6 +188,7 @@ export default class Documents extends Command {
194
188
  if (!data)
195
189
  data = {};
196
190
  const body = data;
191
+ const arrayFields = new Set(endpoint.bodyArrayFields);
197
192
  for (const pair of flags.set) {
198
193
  const eqIndex = pair.indexOf('=');
199
194
  if (eqIndex === -1)
@@ -205,6 +200,19 @@ export default class Documents extends Command {
205
200
  value = JSON.parse(value);
206
201
  }
207
202
  catch { /* keep as string */ }
203
+ // For top-level keys typed as array in the API schema, split bare
204
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
205
+ // wrap a lone scalar in a single-element array. Skips when --set
206
+ // already provided a JSON array.
207
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
208
+ if (isArrayField && !Array.isArray(value)) {
209
+ if (typeof value === 'string') {
210
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
211
+ }
212
+ else if (value !== undefined && value !== null) {
213
+ value = [value];
214
+ }
215
+ }
208
216
  // Support nested keys like "address.city"
209
217
  const keys = key.split('.');
210
218
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -255,6 +263,26 @@ export default class Documents extends Command {
255
263
  process.stderr.write('\n');
256
264
  }
257
265
  try {
266
+ // Binary response: download to file
267
+ if (endpoint.isBinaryResponse) {
268
+ if (!flags.file) {
269
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
270
+ }
271
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
272
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
273
+ data,
274
+ contextName: flags.context,
275
+ binary: true,
276
+ });
277
+ const fsModule = await import('node:fs');
278
+ const binData = binResponse.data;
279
+ const buffer = Buffer.isBuffer(binData)
280
+ ? binData
281
+ : Buffer.from(binData);
282
+ fsModule.writeFileSync(flags.file, buffer);
283
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
284
+ return;
285
+ }
258
286
  const response = await apiRequest(endpoint.method, apiPath, {
259
287
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
260
288
  data,
@@ -345,6 +373,7 @@ export default class Documents extends Command {
345
373
  }
346
374
  else if (flags.set && flags.set.length > 0) {
347
375
  const item = {};
376
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
348
377
  for (const pair of flags.set) {
349
378
  const eqIndex = pair.indexOf('=');
350
379
  if (eqIndex === -1)
@@ -355,6 +384,15 @@ export default class Documents extends Command {
355
384
  value = JSON.parse(value);
356
385
  }
357
386
  catch { /* keep as string */ }
387
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
388
+ if (isArrayField && !Array.isArray(value)) {
389
+ if (typeof value === 'string') {
390
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
391
+ }
392
+ else if (value !== undefined && value !== null) {
393
+ value = [value];
394
+ }
395
+ }
358
396
  const keys = key.split('.');
359
397
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
360
398
  this.error(`Invalid --set key: ${key}`);
@@ -504,14 +542,7 @@ export default class Documents extends Command {
504
542
  if (eqIndex === -1)
505
543
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
506
544
  const key = f.slice(0, eqIndex);
507
- let value = f.slice(eqIndex + 1);
508
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
509
- if (opMatch) {
510
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
511
- }
512
- else {
513
- value = `\$eq:${value}`;
514
- }
545
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
515
546
  const filterKey = `filter.${key}`;
516
547
  if (filterKey in baseParams) {
517
548
  const existing = baseParams[filterKey];
@@ -688,6 +719,7 @@ export default class Documents extends Command {
688
719
  }
689
720
  else if (flags.set && flags.set.length > 0) {
690
721
  const item = {};
722
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
691
723
  for (const pair of flags.set) {
692
724
  const eqIndex = pair.indexOf('=');
693
725
  if (eqIndex === -1)
@@ -698,6 +730,15 @@ export default class Documents extends Command {
698
730
  value = JSON.parse(value);
699
731
  }
700
732
  catch { /* keep as string */ }
733
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
734
+ if (isArrayField && !Array.isArray(value)) {
735
+ if (typeof value === 'string') {
736
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
737
+ }
738
+ else if (value !== undefined && value !== null) {
739
+ value = [value];
740
+ }
741
+ }
701
742
  const keys = key.split('.');
702
743
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
703
744
  this.error(`Invalid --set key: ${key}`);
@@ -817,6 +858,21 @@ export default class Documents extends Command {
817
858
  this.log('');
818
859
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
819
860
  }
861
+ parseFilterValue(value) {
862
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
863
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
864
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
865
+ // no operator prefix is present.
866
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
867
+ const m = value.match(opRe);
868
+ if (!m)
869
+ return `\$eq:${value}`;
870
+ const op = m[1];
871
+ const rest = value.slice(m[0].length);
872
+ if (rest === '')
873
+ return `\$${op}`;
874
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
875
+ }
820
876
  filterFields(data, fields) {
821
877
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
822
878
  const pick = (obj, keys) => {
@@ -30,6 +30,7 @@ export default class DunningConfigs extends Command {
30
30
  private runExport;
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
+ private parseFilterValue;
33
34
  private filterFields;
34
35
  private sanitizeOutput;
35
36
  private getColumns;