@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.
Files changed (191) hide show
  1. package/README.md +6 -0
  2. package/dist/commands/access-groups 3.d.ts +40 -0
  3. package/dist/commands/{access-groups 2.js → access-groups 3.js } +106 -40
  4. package/dist/commands/access-groups.d.ts +40 -0
  5. package/dist/commands/access-groups.js +1074 -0
  6. package/dist/commands/account-transactions.d.ts +42 -0
  7. package/dist/commands/account-transactions.js +1088 -0
  8. package/dist/commands/accounts 2.d.ts +41 -0
  9. package/dist/commands/accounts 2.js +1078 -0
  10. package/dist/commands/accounts.d.ts +41 -0
  11. package/dist/commands/accounts.js +1080 -0
  12. package/dist/commands/agent-runs.d.ts +40 -0
  13. package/dist/commands/agent-runs.js +1071 -0
  14. package/dist/commands/async-endpoints-internal 3.d.ts +40 -0
  15. package/dist/commands/{processing-logs.js → async-endpoints-internal 3.js } +110 -46
  16. package/dist/commands/async-endpoints-internal.d.ts +40 -0
  17. package/dist/commands/async-endpoints-internal.js +1064 -0
  18. package/dist/commands/audit-log 3.d.ts +40 -0
  19. package/dist/commands/audit-log 3.js +1066 -0
  20. package/dist/commands/audit-log.d.ts +40 -0
  21. package/dist/commands/audit-log.js +1068 -0
  22. package/dist/commands/auth/login.d.ts +14 -0
  23. package/dist/commands/auth/login.js +116 -0
  24. package/dist/commands/auth/logout.d.ts +10 -0
  25. package/dist/commands/auth/logout.js +58 -0
  26. package/dist/commands/auth/status.d.ts +12 -0
  27. package/dist/commands/auth/status.js +101 -0
  28. package/dist/commands/auth/tenant.d.ts +19 -0
  29. package/dist/commands/auth/tenant.js +184 -0
  30. package/dist/commands/auth.d.ts +37 -0
  31. package/dist/commands/auth.js +871 -0
  32. package/dist/commands/bank-accounts.d.ts +42 -0
  33. package/dist/commands/bank-accounts.js +1088 -0
  34. package/dist/commands/bank-integrations.d.ts +1 -0
  35. package/dist/commands/bank-integrations.js +122 -54
  36. package/dist/commands/business-partners 3.d.ts +41 -0
  37. package/dist/commands/business-partners 3.js +1078 -0
  38. package/dist/commands/business-partners---contacts 3.d.ts +42 -0
  39. package/dist/commands/business-partners---contacts 3.js +1072 -0
  40. package/dist/commands/business-partners---contacts.d.ts +1 -0
  41. package/dist/commands/business-partners---contacts.js +114 -46
  42. package/dist/commands/business-partners.d.ts +1 -0
  43. package/dist/commands/business-partners.js +114 -46
  44. package/dist/commands/config/add.d.ts +13 -0
  45. package/dist/commands/config/add.js +120 -0
  46. package/dist/commands/config/current.d.ts +9 -0
  47. package/dist/commands/config/current.js +43 -0
  48. package/dist/commands/config/delete.d.ts +12 -0
  49. package/dist/commands/config/delete.js +67 -0
  50. package/dist/commands/config/list.d.ts +9 -0
  51. package/dist/commands/config/list.js +50 -0
  52. package/dist/commands/config/use.d.ts +9 -0
  53. package/dist/commands/config/use.js +40 -0
  54. package/dist/commands/credit-card-accounts 3.d.ts +42 -0
  55. package/dist/commands/credit-card-accounts 3.js +1084 -0
  56. package/dist/commands/credit-card-accounts.d.ts +1 -0
  57. package/dist/commands/credit-card-accounts.js +119 -51
  58. package/dist/commands/documents.d.ts +1 -0
  59. package/dist/commands/documents.js +110 -42
  60. package/dist/commands/dunning-configs 3.d.ts +40 -0
  61. package/dist/commands/dunning-configs 3.js +1072 -0
  62. package/dist/commands/dunning-configs.d.ts +1 -0
  63. package/dist/commands/dunning-configs.js +109 -41
  64. package/dist/commands/dunning-reminders.d.ts +1 -0
  65. package/dist/commands/dunning-reminders.js +112 -44
  66. package/dist/commands/dunning-runs 3.d.ts +40 -0
  67. package/dist/commands/{pubsub.js → dunning-runs 3.js } +108 -43
  68. package/dist/commands/dunning-runs.d.ts +1 -0
  69. package/dist/commands/dunning-runs.js +105 -37
  70. package/dist/commands/employees.d.ts +1 -0
  71. package/dist/commands/employees.js +136 -68
  72. package/dist/commands/event-processor-mappings 3.d.ts +40 -0
  73. package/dist/commands/event-processor-mappings 3.js +1071 -0
  74. package/dist/commands/event-processor-mappings.d.ts +1 -0
  75. package/dist/commands/event-processor-mappings.js +109 -41
  76. package/dist/commands/files-internal 3.d.ts +40 -0
  77. package/dist/commands/files-internal 3.js +1073 -0
  78. package/dist/commands/files-internal.d.ts +1 -0
  79. package/dist/commands/files-internal.js +113 -45
  80. package/dist/commands/health 3.d.ts +40 -0
  81. package/dist/commands/health 3.js +1067 -0
  82. package/dist/commands/health.d.ts +1 -0
  83. package/dist/commands/health.js +109 -41
  84. package/dist/commands/{pubsub.d.ts → inbox 3.d.ts } +2 -1
  85. package/dist/commands/inbox 3.js +1065 -0
  86. package/dist/commands/inbox.d.ts +1 -0
  87. package/dist/commands/inbox.js +110 -40
  88. package/dist/commands/integrations 3.d.ts +41 -0
  89. package/dist/commands/integrations 3.js +1067 -0
  90. package/dist/commands/integrations.d.ts +1 -0
  91. package/dist/commands/integrations.js +107 -39
  92. package/dist/commands/item-categories 3.d.ts +40 -0
  93. package/dist/commands/item-categories 3.js +1072 -0
  94. package/dist/commands/item-categories.d.ts +1 -0
  95. package/dist/commands/item-categories.js +109 -41
  96. package/dist/commands/items 3.d.ts +40 -0
  97. package/dist/commands/items 3.js +1072 -0
  98. package/dist/commands/items.d.ts +1 -0
  99. package/dist/commands/items.js +109 -41
  100. package/dist/commands/journal-lines.d.ts +1 -0
  101. package/dist/commands/journal-lines.js +105 -37
  102. package/dist/commands/journals 3.d.ts +42 -0
  103. package/dist/commands/journals 3.js +1088 -0
  104. package/dist/commands/journals.d.ts +1 -0
  105. package/dist/commands/journals.js +123 -55
  106. package/dist/commands/ledgers.d.ts +1 -0
  107. package/dist/commands/ledgers.js +120 -52
  108. package/dist/commands/office-locations 3.d.ts +40 -0
  109. package/dist/commands/office-locations 3.js +1072 -0
  110. package/dist/commands/office-locations.d.ts +1 -0
  111. package/dist/commands/office-locations.js +109 -41
  112. package/dist/commands/payment-method-ledger-mappings 3.d.ts +41 -0
  113. package/dist/commands/payment-method-ledger-mappings 3.js +1066 -0
  114. package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
  115. package/dist/commands/payment-method-ledger-mappings.js +109 -41
  116. package/dist/commands/payment-methods 3.d.ts +40 -0
  117. package/dist/commands/payment-methods 3.js +1072 -0
  118. package/dist/commands/payment-methods.d.ts +1 -0
  119. package/dist/commands/payment-methods.js +109 -41
  120. package/dist/commands/payment-runs 3.d.ts +42 -0
  121. package/dist/commands/payment-runs 3.js +1087 -0
  122. package/dist/commands/payment-runs.d.ts +1 -0
  123. package/dist/commands/payment-runs.js +122 -54
  124. package/dist/commands/payment-terms 3.d.ts +40 -0
  125. package/dist/commands/payment-terms 3.js +1073 -0
  126. package/dist/commands/payment-terms.d.ts +1 -0
  127. package/dist/commands/payment-terms.js +110 -42
  128. package/dist/commands/price-lists 3.d.ts +41 -0
  129. package/dist/commands/price-lists 3.js +1078 -0
  130. package/dist/commands/price-lists.d.ts +1 -0
  131. package/dist/commands/price-lists.js +114 -46
  132. package/dist/commands/processor-mappings 2.d.ts +40 -0
  133. package/dist/commands/processor-mappings 2.js +1070 -0
  134. package/dist/commands/processor-mappings.d.ts +1 -0
  135. package/dist/commands/processor-mappings.js +108 -40
  136. package/dist/commands/processors.d.ts +1 -0
  137. package/dist/commands/processors.js +113 -45
  138. package/dist/commands/reconciliation 3.d.ts +40 -0
  139. package/dist/commands/reconciliation 3.js +1069 -0
  140. package/dist/commands/reconciliation.d.ts +1 -0
  141. package/dist/commands/reconciliation.js +110 -42
  142. package/dist/commands/reports.d.ts +1 -0
  143. package/dist/commands/reports.js +110 -39
  144. package/dist/commands/salary-types 3.d.ts +40 -0
  145. package/dist/commands/salary-types 3.js +1072 -0
  146. package/dist/commands/salary-types.d.ts +1 -0
  147. package/dist/commands/salary-types.js +1074 -0
  148. package/dist/commands/sales-invoices 3.d.ts +43 -0
  149. package/dist/commands/sales-invoices 3.js +1100 -0
  150. package/dist/commands/sales-invoices.d.ts +1 -0
  151. package/dist/commands/sales-invoices.js +134 -66
  152. package/dist/commands/sales-orders 3.d.ts +43 -0
  153. package/dist/commands/sales-orders 3.js +1098 -0
  154. package/dist/commands/sales-orders.d.ts +1 -0
  155. package/dist/commands/sales-orders.js +133 -64
  156. package/dist/commands/switch.d.ts +1 -0
  157. package/dist/commands/switch.js +58 -3
  158. package/dist/commands/{processing-logs.d.ts → tags 3.d.ts } +2 -1
  159. package/dist/commands/tags 3.js +1072 -0
  160. package/dist/commands/tags.d.ts +1 -0
  161. package/dist/commands/tags.js +109 -41
  162. package/dist/commands/tax-codes 3.d.ts +40 -0
  163. package/dist/commands/tax-codes 3.js +1075 -0
  164. package/dist/commands/tax-codes.d.ts +1 -0
  165. package/dist/commands/tax-codes.js +112 -44
  166. package/dist/commands/tax-matrix.d.ts +1 -0
  167. package/dist/commands/tax-matrix.js +109 -41
  168. package/dist/commands/tenants.d.ts +1 -0
  169. package/dist/commands/tenants.js +109 -41
  170. package/dist/commands/text-templates 3.d.ts +40 -0
  171. package/dist/commands/text-templates 3.js +1072 -0
  172. package/dist/commands/text-templates.d.ts +1 -0
  173. package/dist/commands/text-templates.js +109 -41
  174. package/dist/commands/tools.d.ts +1 -0
  175. package/dist/commands/tools.js +106 -38
  176. package/dist/commands/vendor-invoices.d.ts +1 -0
  177. package/dist/commands/vendor-invoices.js +139 -69
  178. package/dist/commands/webhooks 3.d.ts +40 -0
  179. package/dist/commands/webhooks 3.js +1074 -0
  180. package/dist/commands/webhooks.d.ts +1 -0
  181. package/dist/commands/webhooks.js +111 -43
  182. package/dist/commands/workflows.d.ts +1 -0
  183. package/dist/commands/workflows.js +107 -39
  184. package/dist/lib/api-client.d.ts +5 -1
  185. package/dist/lib/api-client.js +19 -5
  186. package/dist/lib/auth.d.ts +16 -1
  187. package/dist/lib/auth.js +170 -45
  188. package/dist/lib/config.d.ts +7 -0
  189. package/dist/lib/config.js +7 -1
  190. package/oclif.manifest.json +7464 -653
  191. package/package.json +2 -2
@@ -30,6 +30,7 @@ export default class ProcessorMappings 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,10 +8,10 @@ const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = [];
9
9
  const SUBCOMMANDS = [...["list", "create", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false },
13
- 'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false },
14
- 'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['documentTypes'] },
13
+ 'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['documentTypes'] },
14
+ 'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
15
  };
16
16
  export default class ProcessorMappings extends Command {
17
17
  static description = 'Manage processor mappings';
@@ -128,15 +128,7 @@ export default class ProcessorMappings extends Command {
128
128
  if (eqIndex === -1)
129
129
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
130
130
  const key = f.slice(0, eqIndex);
131
- let value = f.slice(eqIndex + 1);
132
- // Check for operator prefix (with or without $)
133
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
134
- if (opMatch) {
135
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
136
- }
137
- else {
138
- value = `\$eq:${value}`;
139
- }
131
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
140
132
  const filterKey = `filter.${key}`;
141
133
  if (filterKey in queryParams) {
142
134
  // Multiple filters on same field: collect as array
@@ -169,7 +161,9 @@ export default class ProcessorMappings extends Command {
169
161
  data = form;
170
162
  }
171
163
  else if (endpoint.hasBody) {
172
- if (flags.file) {
164
+ // For binary-response endpoints, --file is the output path, not a JSON
165
+ // input file — skip the read-as-body branch.
166
+ if (flags.file && !endpoint.isBinaryResponse) {
173
167
  const fsModule = await import('node:fs');
174
168
  try {
175
169
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -191,6 +185,7 @@ export default class ProcessorMappings extends Command {
191
185
  if (!data)
192
186
  data = {};
193
187
  const body = data;
188
+ const arrayFields = new Set(endpoint.bodyArrayFields);
194
189
  for (const pair of flags.set) {
195
190
  const eqIndex = pair.indexOf('=');
196
191
  if (eqIndex === -1)
@@ -202,6 +197,19 @@ export default class ProcessorMappings extends Command {
202
197
  value = JSON.parse(value);
203
198
  }
204
199
  catch { /* keep as string */ }
200
+ // For top-level keys typed as array in the API schema, split bare
201
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
202
+ // wrap a lone scalar in a single-element array. Skips when --set
203
+ // already provided a JSON array.
204
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
205
+ if (isArrayField && !Array.isArray(value)) {
206
+ if (typeof value === 'string') {
207
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
208
+ }
209
+ else if (value !== undefined && value !== null) {
210
+ value = [value];
211
+ }
212
+ }
205
213
  // Support nested keys like "address.city"
206
214
  const keys = key.split('.');
207
215
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -252,6 +260,26 @@ export default class ProcessorMappings extends Command {
252
260
  process.stderr.write('\n');
253
261
  }
254
262
  try {
263
+ // Binary response: download to file
264
+ if (endpoint.isBinaryResponse) {
265
+ if (!flags.file) {
266
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
267
+ }
268
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
269
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
270
+ data,
271
+ contextName: flags.context,
272
+ binary: true,
273
+ });
274
+ const fsModule = await import('node:fs');
275
+ const binData = binResponse.data;
276
+ const buffer = Buffer.isBuffer(binData)
277
+ ? binData
278
+ : Buffer.from(binData);
279
+ fsModule.writeFileSync(flags.file, buffer);
280
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
281
+ return;
282
+ }
255
283
  const response = await apiRequest(endpoint.method, apiPath, {
256
284
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
257
285
  data,
@@ -342,6 +370,7 @@ export default class ProcessorMappings extends Command {
342
370
  }
343
371
  else if (flags.set && flags.set.length > 0) {
344
372
  const item = {};
373
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
345
374
  for (const pair of flags.set) {
346
375
  const eqIndex = pair.indexOf('=');
347
376
  if (eqIndex === -1)
@@ -352,6 +381,15 @@ export default class ProcessorMappings extends Command {
352
381
  value = JSON.parse(value);
353
382
  }
354
383
  catch { /* keep as string */ }
384
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
385
+ if (isArrayField && !Array.isArray(value)) {
386
+ if (typeof value === 'string') {
387
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
388
+ }
389
+ else if (value !== undefined && value !== null) {
390
+ value = [value];
391
+ }
392
+ }
355
393
  const keys = key.split('.');
356
394
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
357
395
  this.error(`Invalid --set key: ${key}`);
@@ -501,14 +539,7 @@ export default class ProcessorMappings extends Command {
501
539
  if (eqIndex === -1)
502
540
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
503
541
  const key = f.slice(0, eqIndex);
504
- let value = f.slice(eqIndex + 1);
505
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
506
- if (opMatch) {
507
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
508
- }
509
- else {
510
- value = `\$eq:${value}`;
511
- }
542
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
512
543
  const filterKey = `filter.${key}`;
513
544
  if (filterKey in baseParams) {
514
545
  const existing = baseParams[filterKey];
@@ -685,6 +716,7 @@ export default class ProcessorMappings extends Command {
685
716
  }
686
717
  else if (flags.set && flags.set.length > 0) {
687
718
  const item = {};
719
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
688
720
  for (const pair of flags.set) {
689
721
  const eqIndex = pair.indexOf('=');
690
722
  if (eqIndex === -1)
@@ -695,6 +727,15 @@ export default class ProcessorMappings extends Command {
695
727
  value = JSON.parse(value);
696
728
  }
697
729
  catch { /* keep as string */ }
730
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
731
+ if (isArrayField && !Array.isArray(value)) {
732
+ if (typeof value === 'string') {
733
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
734
+ }
735
+ else if (value !== undefined && value !== null) {
736
+ value = [value];
737
+ }
738
+ }
698
739
  const keys = key.split('.');
699
740
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
700
741
  this.error(`Invalid --set key: ${key}`);
@@ -814,35 +855,62 @@ export default class ProcessorMappings extends Command {
814
855
  this.log('');
815
856
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
816
857
  }
858
+ parseFilterValue(value) {
859
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
860
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
861
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
862
+ // no operator prefix is present.
863
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
864
+ const m = value.match(opRe);
865
+ if (!m)
866
+ return `\$eq:${value}`;
867
+ const op = m[1];
868
+ const rest = value.slice(m[0].length);
869
+ if (rest === '')
870
+ return `\$${op}`;
871
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
872
+ }
817
873
  filterFields(data, fields) {
818
874
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
819
875
  const pick = (obj, keys) => {
820
- const result = {};
876
+ // Group keys by their first segment so sibling sub-paths
877
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
878
+ // and don't clobber each other on merge.
879
+ const directKeys = new Set();
880
+ const nestedKeys = new Map();
821
881
  for (const key of keys) {
822
882
  if (key.split('.').some(isUnsafeKey))
823
883
  continue;
824
884
  if (key.includes('.')) {
825
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
826
885
  const [first, ...rest] = key.split('.');
827
- const val = obj[first];
828
- if (val && typeof val === 'object') {
829
- const restKey = rest.join('.');
830
- if (Array.isArray(val)) {
831
- // Map over array elements (e.g., allocations[].salesInvoiceId)
832
- const existing = Array.isArray(result[first]) ? result[first] : [];
833
- result[first] = val.map((item, i) => ({
834
- ...(existing[i] || {}),
835
- ...pick(item, [restKey]),
836
- }));
837
- }
838
- else {
839
- const nested = pick(val, [restKey]);
840
- result[first] = { ...(result[first] || {}), ...nested };
841
- }
886
+ if (isUnsafeKey(first))
887
+ continue;
888
+ const list = nestedKeys.get(first);
889
+ if (list) {
890
+ list.push(rest.join('.'));
891
+ }
892
+ else {
893
+ nestedKeys.set(first, [rest.join('.')]);
842
894
  }
843
895
  }
844
- else if (key in obj) {
896
+ else {
897
+ directKeys.add(key);
898
+ }
899
+ }
900
+ const result = {};
901
+ for (const key of directKeys) {
902
+ if (key in obj)
845
903
  result[key] = obj[key];
904
+ }
905
+ for (const [first, restKeys] of nestedKeys) {
906
+ const val = obj[first];
907
+ if (!val || typeof val !== 'object')
908
+ continue;
909
+ if (Array.isArray(val)) {
910
+ result[first] = val.map(item => pick(item, restKeys));
911
+ }
912
+ else {
913
+ result[first] = pick(val, restKeys);
846
914
  }
847
915
  }
848
916
  return result;
@@ -30,6 +30,7 @@ export default class Processors 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,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 all processors (tenant and system)', 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';
@@ -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
- let value = f.slice(eqIndex + 1);
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
- if (flags.file) {
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
- let value = f.slice(eqIndex + 1);
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,35 +861,62 @@ 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) => {
826
- const result = {};
882
+ // Group keys by their first segment so sibling sub-paths
883
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
884
+ // and don't clobber each other on merge.
885
+ const directKeys = new Set();
886
+ const nestedKeys = new Map();
827
887
  for (const key of keys) {
828
888
  if (key.split('.').some(isUnsafeKey))
829
889
  continue;
830
890
  if (key.includes('.')) {
831
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
832
891
  const [first, ...rest] = key.split('.');
833
- const val = obj[first];
834
- if (val && typeof val === 'object') {
835
- const restKey = rest.join('.');
836
- if (Array.isArray(val)) {
837
- // Map over array elements (e.g., allocations[].salesInvoiceId)
838
- const existing = Array.isArray(result[first]) ? result[first] : [];
839
- result[first] = val.map((item, i) => ({
840
- ...(existing[i] || {}),
841
- ...pick(item, [restKey]),
842
- }));
843
- }
844
- else {
845
- const nested = pick(val, [restKey]);
846
- result[first] = { ...(result[first] || {}), ...nested };
847
- }
892
+ if (isUnsafeKey(first))
893
+ continue;
894
+ const list = nestedKeys.get(first);
895
+ if (list) {
896
+ list.push(rest.join('.'));
897
+ }
898
+ else {
899
+ nestedKeys.set(first, [rest.join('.')]);
848
900
  }
849
901
  }
850
- else if (key in obj) {
902
+ else {
903
+ directKeys.add(key);
904
+ }
905
+ }
906
+ const result = {};
907
+ for (const key of directKeys) {
908
+ if (key in obj)
851
909
  result[key] = obj[key];
910
+ }
911
+ for (const [first, restKeys] of nestedKeys) {
912
+ const val = obj[first];
913
+ if (!val || typeof val !== 'object')
914
+ continue;
915
+ if (Array.isArray(val)) {
916
+ result[first] = val.map(item => pick(item, restKeys));
917
+ }
918
+ else {
919
+ result[first] = pick(val, restKeys);
852
920
  }
853
921
  }
854
922
  return result;
@@ -0,0 +1,40 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Reconciliation extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ 'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ };
28
+ run(): Promise<void>;
29
+ private runImport;
30
+ private runExport;
31
+ private runCamt053Import;
32
+ private runNestedImport;
33
+ private parseFilterValue;
34
+ private filterFields;
35
+ private sanitizeOutput;
36
+ private getColumns;
37
+ private getValue;
38
+ private printTable;
39
+ private printCsv;
40
+ }