@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
@@ -33,6 +33,7 @@ export default class SalesOrders extends Command {
33
33
  private runExport;
34
34
  private runCamt053Import;
35
35
  private runNestedImport;
36
+ private parseFilterValue;
36
37
  private filterFields;
37
38
  private sanitizeOutput;
38
39
  private getColumns;
@@ -6,35 +6,36 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
9
+ const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "pdf", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
- 'list': { method: 'GET', path: '/v1/sales-orders', summary: 'Get all sales orders with pagination', pathParams: [], hasBody: false, isMultipart: false },
12
- 'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false },
13
- 'get': { method: 'GET', path: '/v1/sales-orders/{id}', summary: 'Get a sales order by ID', pathParams: ['id'], hasBody: false, isMultipart: false },
14
- 'update': { method: 'PATCH', path: '/v1/sales-orders/{id}', summary: 'Update a sales order', pathParams: ['id'], hasBody: true, isMultipart: false },
15
- 'delete': { method: 'DELETE', path: '/v1/sales-orders/{id}', summary: 'Delete a sales order', pathParams: ['id'], hasBody: false, isMultipart: false },
16
- 'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false },
17
- 'approve': { method: 'POST', path: '/v1/sales-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number.', pathParams: ['id'], hasBody: false, isMultipart: false },
18
- 'accept': { method: 'POST', path: '/v1/sales-orders/{id}/accept', summary: 'Accept order (APPROVED → ACCEPTED). Adjusts billing start date.', pathParams: ['id'], hasBody: false, isMultipart: false },
19
- 'terminate': { method: 'POST', path: '/v1/sales-orders/{id}/terminate', summary: 'Terminate order (ACCEPTED → TERMINATED). Requires billingEndDate.', pathParams: ['id'], hasBody: true, isMultipart: false },
20
- 'cancel': { method: 'POST', path: '/v1/sales-orders/{id}/cancel', summary: 'Cancel order (any pre-active status → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false },
21
- 'replace': { method: 'POST', path: '/v1/sales-orders/{id}/replace', summary: 'Replace order with corrected version (APPROVED → creates new DRAFT)', pathParams: ['id'], hasBody: false, isMultipart: false },
22
- 'create-invoice': { method: 'POST', path: '/v1/sales-orders/{id}/create-invoice', summary: 'Create invoice from sales order (ACCEPTED → creates DRAFT invoice)', pathParams: ['id'], hasBody: false, isMultipart: false },
23
- 'create-line-items': { method: 'POST', path: '/v1/sales-orders/{orderId}/line-items', summary: 'Add a line item to a sales order', pathParams: ['orderId'], hasBody: true, isMultipart: false },
24
- 'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['orderId'], hasBody: true, isMultipart: false },
25
- 'update-line-items': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false },
26
- 'delete-line-items': { method: 'DELETE', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false },
27
- 'list-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Get all notes for a sales order', pathParams: ['salesOrderId'], hasBody: false, isMultipart: false },
28
- 'create-notes': { method: 'POST', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Create a note for a sales order', pathParams: ['salesOrderId'], hasBody: true, isMultipart: false },
29
- 'get-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Get a specific note for a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: false, isMultipart: false },
30
- 'update-notes': { method: 'PATCH', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Update a note for a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: true, isMultipart: false },
31
- 'delete-notes': { method: 'DELETE', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Delete a note from a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: false, isMultipart: false },
32
- 'list-files': { method: 'GET', path: '/v1/sales-orders/{parentId}/files', summary: 'List file attachments for a sales order', pathParams: ['parentId'], hasBody: false, isMultipart: false },
33
- 'create-files': { method: 'POST', path: '/v1/sales-orders/{parentId}/files', summary: 'Upload a file attachment to a sales order', pathParams: ['parentId'], hasBody: false, isMultipart: true },
34
- 'get-files': { method: 'GET', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Get a file attachment for a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false },
35
- 'update-files': { method: 'PATCH', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a sales order', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false },
36
- 'delete-files': { method: 'DELETE', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false },
37
- 'files-download': { method: 'GET', path: '/v1/sales-orders/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false },
11
+ 'list': { method: 'GET', path: '/v1/sales-orders', summary: 'Get all sales orders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
+ 'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'lineItems'] },
13
+ 'get': { method: 'GET', path: '/v1/sales-orders/{id}', summary: 'Get a sales order by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
14
+ 'update': { method: 'PATCH', path: '/v1/sales-orders/{id}', summary: 'Update a sales order', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'lineItems', 'removeFileIds'] },
15
+ 'delete': { method: 'DELETE', path: '/v1/sales-orders/{id}', summary: 'Delete a sales order', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
+ 'pdf': { method: 'GET', path: '/v1/sales-orders/{id}/pdf', summary: 'Render a sales order PDF using the configured PDF processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
+ 'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
+ 'approve': { method: 'POST', path: '/v1/sales-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
+ 'accept': { method: 'POST', path: '/v1/sales-orders/{id}/accept', summary: 'Accept order (APPROVED → ACCEPTED). Adjusts billing start date.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
+ 'terminate': { method: 'POST', path: '/v1/sales-orders/{id}/terminate', summary: 'Terminate order (ACCEPTED → TERMINATED). Requires billingEndDate.', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
21
+ 'cancel': { method: 'POST', path: '/v1/sales-orders/{id}/cancel', summary: 'Cancel order (any pre-active status → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
+ 'replace': { method: 'POST', path: '/v1/sales-orders/{id}/replace', summary: 'Replace order with corrected version (APPROVED → creates new DRAFT)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
+ 'create-invoice': { method: 'POST', path: '/v1/sales-orders/{id}/create-invoice', summary: 'Create invoice from sales order (ACCEPTED → creates DRAFT invoice)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
24
+ 'create-line-items': { method: 'POST', path: '/v1/sales-orders/{orderId}/line-items', summary: 'Add a line item to a sales order', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
+ 'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['items'] },
26
+ 'update-line-items': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
+ 'delete-line-items': { method: 'DELETE', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
28
+ 'list-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Get all notes for a sales order', pathParams: ['salesOrderId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
29
+ 'create-notes': { method: 'POST', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Create a note for a sales order', pathParams: ['salesOrderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
30
+ 'get-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Get a specific note for a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
31
+ 'update-notes': { method: 'PATCH', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Update a note for a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
32
+ 'delete-notes': { method: 'DELETE', path: '/v1/sales-orders/{salesOrderId}/notes/{noteId}', summary: 'Delete a note from a sales order', pathParams: ['salesOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
33
+ 'list-files': { method: 'GET', path: '/v1/sales-orders/{parentId}/files', summary: 'List file attachments for a sales order', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
34
+ 'create-files': { method: 'POST', path: '/v1/sales-orders/{parentId}/files', summary: 'Upload a file attachment to a sales order', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
35
+ 'get-files': { method: 'GET', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Get a file attachment for a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
36
+ 'update-files': { method: 'PATCH', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a sales order', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
37
+ 'delete-files': { method: 'DELETE', path: '/v1/sales-orders/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
38
+ 'files-download': { method: 'GET', path: '/v1/sales-orders/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a sales order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
38
39
  };
39
40
  export default class SalesOrders extends Command {
40
41
  static description = 'Manage sales orders';
@@ -155,15 +156,7 @@ export default class SalesOrders extends Command {
155
156
  if (eqIndex === -1)
156
157
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
157
158
  const key = f.slice(0, eqIndex);
158
- let value = f.slice(eqIndex + 1);
159
- // Check for operator prefix (with or without $)
160
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
161
- if (opMatch) {
162
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
163
- }
164
- else {
165
- value = `\$eq:${value}`;
166
- }
159
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
167
160
  const filterKey = `filter.${key}`;
168
161
  if (filterKey in queryParams) {
169
162
  // Multiple filters on same field: collect as array
@@ -196,7 +189,9 @@ export default class SalesOrders extends Command {
196
189
  data = form;
197
190
  }
198
191
  else if (endpoint.hasBody) {
199
- if (flags.file) {
192
+ // For binary-response endpoints, --file is the output path, not a JSON
193
+ // input file — skip the read-as-body branch.
194
+ if (flags.file && !endpoint.isBinaryResponse) {
200
195
  const fsModule = await import('node:fs');
201
196
  try {
202
197
  data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
@@ -218,6 +213,7 @@ export default class SalesOrders extends Command {
218
213
  if (!data)
219
214
  data = {};
220
215
  const body = data;
216
+ const arrayFields = new Set(endpoint.bodyArrayFields);
221
217
  for (const pair of flags.set) {
222
218
  const eqIndex = pair.indexOf('=');
223
219
  if (eqIndex === -1)
@@ -229,6 +225,19 @@ export default class SalesOrders extends Command {
229
225
  value = JSON.parse(value);
230
226
  }
231
227
  catch { /* keep as string */ }
228
+ // For top-level keys typed as array in the API schema, split bare
229
+ // comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
230
+ // wrap a lone scalar in a single-element array. Skips when --set
231
+ // already provided a JSON array.
232
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
233
+ if (isArrayField && !Array.isArray(value)) {
234
+ if (typeof value === 'string') {
235
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
236
+ }
237
+ else if (value !== undefined && value !== null) {
238
+ value = [value];
239
+ }
240
+ }
232
241
  // Support nested keys like "address.city"
233
242
  const keys = key.split('.');
234
243
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
@@ -279,6 +288,26 @@ export default class SalesOrders extends Command {
279
288
  process.stderr.write('\n');
280
289
  }
281
290
  try {
291
+ // Binary response: download to file
292
+ if (endpoint.isBinaryResponse) {
293
+ if (!flags.file) {
294
+ this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
295
+ }
296
+ const binResponse = await apiRequest(endpoint.method, apiPath, {
297
+ params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
298
+ data,
299
+ contextName: flags.context,
300
+ binary: true,
301
+ });
302
+ const fsModule = await import('node:fs');
303
+ const binData = binResponse.data;
304
+ const buffer = Buffer.isBuffer(binData)
305
+ ? binData
306
+ : Buffer.from(binData);
307
+ fsModule.writeFileSync(flags.file, buffer);
308
+ this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
309
+ return;
310
+ }
282
311
  const response = await apiRequest(endpoint.method, apiPath, {
283
312
  params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
284
313
  data,
@@ -369,6 +398,7 @@ export default class SalesOrders extends Command {
369
398
  }
370
399
  else if (flags.set && flags.set.length > 0) {
371
400
  const item = {};
401
+ const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
372
402
  for (const pair of flags.set) {
373
403
  const eqIndex = pair.indexOf('=');
374
404
  if (eqIndex === -1)
@@ -379,6 +409,15 @@ export default class SalesOrders extends Command {
379
409
  value = JSON.parse(value);
380
410
  }
381
411
  catch { /* keep as string */ }
412
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
413
+ if (isArrayField && !Array.isArray(value)) {
414
+ if (typeof value === 'string') {
415
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
416
+ }
417
+ else if (value !== undefined && value !== null) {
418
+ value = [value];
419
+ }
420
+ }
382
421
  const keys = key.split('.');
383
422
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
384
423
  this.error(`Invalid --set key: ${key}`);
@@ -528,14 +567,7 @@ export default class SalesOrders extends Command {
528
567
  if (eqIndex === -1)
529
568
  this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
530
569
  const key = f.slice(0, eqIndex);
531
- let value = f.slice(eqIndex + 1);
532
- const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
533
- if (opMatch) {
534
- value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
535
- }
536
- else {
537
- value = `\$eq:${value}`;
538
- }
570
+ const value = this.parseFilterValue(f.slice(eqIndex + 1));
539
571
  const filterKey = `filter.${key}`;
540
572
  if (filterKey in baseParams) {
541
573
  const existing = baseParams[filterKey];
@@ -712,6 +744,7 @@ export default class SalesOrders extends Command {
712
744
  }
713
745
  else if (flags.set && flags.set.length > 0) {
714
746
  const item = {};
747
+ const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
715
748
  for (const pair of flags.set) {
716
749
  const eqIndex = pair.indexOf('=');
717
750
  if (eqIndex === -1)
@@ -722,6 +755,15 @@ export default class SalesOrders extends Command {
722
755
  value = JSON.parse(value);
723
756
  }
724
757
  catch { /* keep as string */ }
758
+ const isArrayField = !key.includes('.') && arrayFields.has(key);
759
+ if (isArrayField && !Array.isArray(value)) {
760
+ if (typeof value === 'string') {
761
+ value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
762
+ }
763
+ else if (value !== undefined && value !== null) {
764
+ value = [value];
765
+ }
766
+ }
725
767
  const keys = key.split('.');
726
768
  if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
727
769
  this.error(`Invalid --set key: ${key}`);
@@ -841,35 +883,62 @@ export default class SalesOrders extends Command {
841
883
  this.log('');
842
884
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
843
885
  }
886
+ parseFilterValue(value) {
887
+ // Recognises the nestjs-paginate operator set (with or without the leading $)
888
+ // and recursively unwraps chained operators like 'not:null' -> '$not:$null'
889
+ // or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
890
+ // no operator prefix is present.
891
+ const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
892
+ const m = value.match(opRe);
893
+ if (!m)
894
+ return `\$eq:${value}`;
895
+ const op = m[1];
896
+ const rest = value.slice(m[0].length);
897
+ if (rest === '')
898
+ return `\$${op}`;
899
+ return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
900
+ }
844
901
  filterFields(data, fields) {
845
902
  const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
846
903
  const pick = (obj, keys) => {
847
- const result = {};
904
+ // Group keys by their first segment so sibling sub-paths
905
+ // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
906
+ // and don't clobber each other on merge.
907
+ const directKeys = new Set();
908
+ const nestedKeys = new Map();
848
909
  for (const key of keys) {
849
910
  if (key.split('.').some(isUnsafeKey))
850
911
  continue;
851
912
  if (key.includes('.')) {
852
- // Handle nested fields like 'customer.name' or 'allocations.salesInvoiceId'
853
913
  const [first, ...rest] = key.split('.');
854
- const val = obj[first];
855
- if (val && typeof val === 'object') {
856
- const restKey = rest.join('.');
857
- if (Array.isArray(val)) {
858
- // Map over array elements (e.g., allocations[].salesInvoiceId)
859
- const existing = Array.isArray(result[first]) ? result[first] : [];
860
- result[first] = val.map((item, i) => ({
861
- ...(existing[i] || {}),
862
- ...pick(item, [restKey]),
863
- }));
864
- }
865
- else {
866
- const nested = pick(val, [restKey]);
867
- result[first] = { ...(result[first] || {}), ...nested };
868
- }
914
+ if (isUnsafeKey(first))
915
+ continue;
916
+ const list = nestedKeys.get(first);
917
+ if (list) {
918
+ list.push(rest.join('.'));
919
+ }
920
+ else {
921
+ nestedKeys.set(first, [rest.join('.')]);
869
922
  }
870
923
  }
871
- else if (key in obj) {
924
+ else {
925
+ directKeys.add(key);
926
+ }
927
+ }
928
+ const result = {};
929
+ for (const key of directKeys) {
930
+ if (key in obj)
872
931
  result[key] = obj[key];
932
+ }
933
+ for (const [first, restKeys] of nestedKeys) {
934
+ const val = obj[first];
935
+ if (!val || typeof val !== 'object')
936
+ continue;
937
+ if (Array.isArray(val)) {
938
+ result[first] = val.map(item => pick(item, restKeys));
939
+ }
940
+ else {
941
+ result[first] = pick(val, restKeys);
873
942
  }
874
943
  }
875
944
  return result;
@@ -3,4 +3,5 @@ export default class Switch extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  run(): Promise<void>;
6
+ private handleSessionExpired;
6
7
  }
@@ -1,6 +1,7 @@
1
- import { select } from '@inquirer/prompts';
1
+ import { confirm, select } from '@inquirer/prompts';
2
2
  import { Command } from '@oclif/core';
3
- import { getCurrentContext, listContexts, setCurrentContext } from '../lib/config.js';
3
+ import { loginAndSwitchTenant } from '../lib/auth.js';
4
+ import { getContext, getCurrentContext, listContexts, setCurrentContext } from '../lib/config.js';
4
5
  export default class Switch extends Command {
5
6
  static description = 'Switch context and tenant interactively';
6
7
  static examples = ['<%= config.bin %> switch'];
@@ -34,8 +35,62 @@ export default class Switch extends Command {
34
35
  setCurrentContext(selectedContext);
35
36
  this.log(`Switched to context: ${selectedContext}`);
36
37
  }
37
- // Step 2: Select tenant within chosen context
38
+ // Step 2: Select tenant within chosen context.
39
+ // If the session is expired, prompt the user to re-login and then retry.
38
40
  const { default: AuthTenant } = await import('./auth/tenant.js');
41
+ try {
42
+ await AuthTenant.run([`--context=${selectedContext}`]);
43
+ }
44
+ catch (error) {
45
+ if (!isSessionExpired(error))
46
+ throw error;
47
+ await this.handleSessionExpired(selectedContext);
48
+ }
49
+ }
50
+ async handleSessionExpired(selectedContext) {
51
+ const shouldLogin = await confirm({
52
+ default: true,
53
+ message: 'Session expired. Do you want to login again?',
54
+ });
55
+ if (!shouldLogin)
56
+ return;
57
+ // If we have a cached tenant list from a previous session, let the user pick
58
+ // tenant FIRST. Then run the combined login + tenant-switch flow which uses
59
+ // a single browser tab (chained PKCE rounds with a meta-refresh redirect).
60
+ const ctx = getContext(selectedContext);
61
+ const cached = ctx?.cachedTenants ?? [];
62
+ if (cached.length > 1) {
63
+ const sorted = [...cached].sort((a, b) => {
64
+ if (a.tenantId === ctx?.tenant_id)
65
+ return -1;
66
+ if (b.tenantId === ctx?.tenant_id)
67
+ return 1;
68
+ return a.name.localeCompare(b.name);
69
+ });
70
+ const selectedTenant = await select({
71
+ choices: sorted.map((t) => ({
72
+ name: t.tenantId === ctx?.tenant_id ? `${t.name} (previous)` : t.name,
73
+ value: t.tenantId,
74
+ })),
75
+ message: 'Select a tenant:',
76
+ });
77
+ const selectedName = cached.find((t) => t.tenantId === selectedTenant)?.name;
78
+ this.log(`Logging in and switching to ${selectedName ?? selectedTenant}…`);
79
+ if (ctx) {
80
+ await loginAndSwitchTenant(ctx, selectedTenant);
81
+ }
82
+ this.log(`Successfully switched to tenant ${selectedName ? `${selectedName} (${selectedTenant})` : selectedTenant}`);
83
+ return;
84
+ }
85
+ // No cached tenant list (or only one tenant) — fall back to the two-step
86
+ // flow: login first, then run the normal tenant selector.
87
+ const { default: AuthLogin } = await import('./auth/login.js');
88
+ const { default: AuthTenant } = await import('./auth/tenant.js');
89
+ await AuthLogin.run([`--context=${selectedContext}`]);
39
90
  await AuthTenant.run([`--context=${selectedContext}`]);
40
91
  }
41
92
  }
93
+ function isSessionExpired(error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ return /session expired/i.test(message) || /not logged in/i.test(message);
96
+ }
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class ProcessingLogs extends Command {
2
+ export default class Tags extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -30,6 +30,7 @@ export default class ProcessingLogs 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;