@aderis-erp/cli 1.0.14 → 1.0.17

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 (167) hide show
  1. package/README.md +30 -8
  2. package/dist/commands/access-groups.d.ts +0 -1
  3. package/dist/commands/access-groups.js +22 -78
  4. package/dist/commands/account-transaction-allocations.d.ts +0 -1
  5. package/dist/commands/account-transaction-allocations.js +22 -78
  6. package/dist/commands/account-transactions.d.ts +0 -1
  7. package/dist/commands/account-transactions.js +28 -82
  8. package/dist/commands/accounts.d.ts +0 -1
  9. package/dist/commands/accounts.js +24 -80
  10. package/dist/commands/agent-platform.d.ts +0 -1
  11. package/dist/commands/agent-platform.js +24 -79
  12. package/dist/commands/analytics.d.ts +39 -0
  13. package/dist/commands/analytics.js +1113 -0
  14. package/dist/commands/{documents.d.ts → api-keys.d.ts} +1 -2
  15. package/dist/commands/api-keys.js +1117 -0
  16. package/dist/commands/audit-log.d.ts +0 -1
  17. package/dist/commands/audit-log.js +22 -78
  18. package/dist/commands/auth/login.d.ts +9 -0
  19. package/dist/commands/auth/login.js +83 -21
  20. package/dist/commands/auth/logout.d.ts +6 -0
  21. package/dist/commands/auth/logout.js +23 -6
  22. package/dist/commands/auth/status.js +46 -23
  23. package/dist/commands/auth/tenant.js +15 -9
  24. package/dist/commands/auth.d.ts +0 -1
  25. package/dist/commands/auth.js +37 -51
  26. package/dist/commands/bank-accounts.d.ts +0 -1
  27. package/dist/commands/bank-accounts.js +22 -78
  28. package/dist/commands/bank-integrations.d.ts +0 -1
  29. package/dist/commands/bank-integrations.js +22 -78
  30. package/dist/commands/business-partners.d.ts +1 -1
  31. package/dist/commands/business-partners.js +38 -84
  32. package/dist/commands/chat.d.ts +0 -1
  33. package/dist/commands/chat.js +22 -78
  34. package/dist/commands/config/add.d.ts +12 -0
  35. package/dist/commands/config/add.js +97 -17
  36. package/dist/commands/config/current.js +12 -4
  37. package/dist/commands/config/list.js +12 -4
  38. package/dist/commands/config/set-api-key.d.ts +10 -0
  39. package/dist/commands/config/set-api-key.js +49 -0
  40. package/dist/commands/config/use.js +3 -4
  41. package/dist/commands/credit-card-accounts.d.ts +0 -1
  42. package/dist/commands/credit-card-accounts.js +22 -78
  43. package/dist/commands/delivery-notes.d.ts +0 -1
  44. package/dist/commands/delivery-notes.js +29 -84
  45. package/dist/commands/dunning-configs.d.ts +0 -1
  46. package/dist/commands/dunning-configs.js +22 -78
  47. package/dist/commands/dunning-reminders.d.ts +0 -1
  48. package/dist/commands/dunning-reminders.js +22 -78
  49. package/dist/commands/dunning-runs.d.ts +0 -1
  50. package/dist/commands/dunning-runs.js +22 -78
  51. package/dist/commands/email-transactions.d.ts +0 -1
  52. package/dist/commands/email-transactions.js +22 -78
  53. package/dist/commands/employees.d.ts +0 -1
  54. package/dist/commands/employees.js +24 -80
  55. package/dist/commands/entity-configurations.d.ts +0 -1
  56. package/dist/commands/entity-configurations.js +22 -78
  57. package/dist/commands/event-processor-mappings.d.ts +0 -1
  58. package/dist/commands/event-processor-mappings.js +22 -78
  59. package/dist/commands/{files-internal.d.ts → files.d.ts} +1 -2
  60. package/dist/commands/{files-internal.js → files.js} +40 -90
  61. package/dist/commands/health.d.ts +0 -1
  62. package/dist/commands/health.js +22 -78
  63. package/dist/commands/inbox.d.ts +0 -1
  64. package/dist/commands/inbox.js +22 -78
  65. package/dist/commands/integrations.d.ts +0 -1
  66. package/dist/commands/integrations.js +22 -78
  67. package/dist/commands/item-categories.d.ts +0 -1
  68. package/dist/commands/item-categories.js +22 -78
  69. package/dist/commands/items.d.ts +0 -1
  70. package/dist/commands/items.js +24 -80
  71. package/dist/commands/journal-lines.d.ts +0 -1
  72. package/dist/commands/journal-lines.js +22 -78
  73. package/dist/commands/journals.d.ts +1 -1
  74. package/dist/commands/journals.js +29 -81
  75. package/dist/commands/ledgers.d.ts +0 -1
  76. package/dist/commands/ledgers.js +26 -79
  77. package/dist/commands/nango-integrations.d.ts +39 -0
  78. package/dist/commands/nango-integrations.js +1115 -0
  79. package/dist/commands/office-locations.d.ts +0 -1
  80. package/dist/commands/office-locations.js +22 -78
  81. package/dist/commands/payment-methods.d.ts +0 -1
  82. package/dist/commands/payment-methods.js +22 -78
  83. package/dist/commands/payment-runs.d.ts +0 -1
  84. package/dist/commands/payment-runs.js +24 -79
  85. package/dist/commands/payment-terms.d.ts +0 -1
  86. package/dist/commands/payment-terms.js +22 -78
  87. package/dist/commands/payments.d.ts +0 -1
  88. package/dist/commands/payments.js +22 -78
  89. package/dist/commands/price-lists.d.ts +0 -1
  90. package/dist/commands/price-lists.js +22 -78
  91. package/dist/commands/processor-mappings.d.ts +0 -1
  92. package/dist/commands/processor-mappings.js +22 -78
  93. package/dist/commands/processors.d.ts +0 -1
  94. package/dist/commands/processors.js +22 -78
  95. package/dist/commands/purchase-order-types.d.ts +0 -1
  96. package/dist/commands/purchase-order-types.js +22 -78
  97. package/dist/commands/purchase-orders.d.ts +1 -1
  98. package/dist/commands/purchase-orders.js +35 -84
  99. package/dist/commands/reports.d.ts +0 -1
  100. package/dist/commands/reports.js +23 -79
  101. package/dist/commands/salary-types.d.ts +0 -1
  102. package/dist/commands/salary-types.js +22 -78
  103. package/dist/commands/sales-invoices.d.ts +0 -1
  104. package/dist/commands/sales-invoices.js +38 -89
  105. package/dist/commands/sales-orders.d.ts +0 -1
  106. package/dist/commands/sales-orders.js +26 -82
  107. package/dist/commands/saved-views.d.ts +0 -1
  108. package/dist/commands/saved-views.js +22 -78
  109. package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
  110. package/dist/commands/scheduled-processor-mappings.js +22 -78
  111. package/dist/commands/schedules.d.ts +0 -1
  112. package/dist/commands/schedules.js +22 -78
  113. package/dist/commands/switch.js +14 -7
  114. package/dist/commands/tag-configurations.d.ts +39 -0
  115. package/dist/commands/{documents.js → tag-configurations.js} +40 -96
  116. package/dist/commands/tags.d.ts +0 -1
  117. package/dist/commands/tags.js +22 -78
  118. package/dist/commands/tax-codes.d.ts +0 -1
  119. package/dist/commands/tax-codes.js +22 -78
  120. package/dist/commands/tax-matrix.d.ts +0 -1
  121. package/dist/commands/tax-matrix.js +22 -78
  122. package/dist/commands/tenants.d.ts +0 -1
  123. package/dist/commands/tenants.js +24 -79
  124. package/dist/commands/text-templates.d.ts +0 -1
  125. package/dist/commands/text-templates.js +22 -78
  126. package/dist/commands/tools.d.ts +0 -1
  127. package/dist/commands/tools.js +22 -78
  128. package/dist/commands/vat-declarations.d.ts +1 -1
  129. package/dist/commands/vat-declarations.js +28 -79
  130. package/dist/commands/vendor-invoices.d.ts +0 -1
  131. package/dist/commands/vendor-invoices.js +31 -86
  132. package/dist/commands/webhooks.d.ts +0 -1
  133. package/dist/commands/webhooks.js +22 -78
  134. package/dist/commands/workflows.d.ts +0 -1
  135. package/dist/commands/workflows.js +22 -78
  136. package/dist/lib/api-client.js +8 -6
  137. package/dist/lib/auth/constants.d.ts +11 -0
  138. package/dist/lib/auth/constants.js +11 -0
  139. package/dist/lib/auth/errors.d.ts +17 -0
  140. package/dist/lib/auth/errors.js +22 -0
  141. package/dist/lib/auth/flow.d.ts +27 -0
  142. package/dist/lib/auth/flow.js +163 -0
  143. package/dist/lib/auth/guards.d.ts +5 -0
  144. package/dist/lib/auth/guards.js +14 -0
  145. package/dist/lib/auth/http.d.ts +16 -0
  146. package/dist/lib/auth/http.js +49 -0
  147. package/dist/lib/auth/index.d.ts +49 -0
  148. package/dist/lib/auth/index.js +217 -0
  149. package/dist/lib/auth/keycloak-provider.d.ts +75 -0
  150. package/dist/lib/auth/keycloak-provider.js +215 -0
  151. package/dist/lib/auth/pages.d.ts +25 -0
  152. package/dist/lib/auth/pages.js +123 -0
  153. package/dist/lib/auth/pkce.d.ts +11 -0
  154. package/dist/lib/auth/pkce.js +17 -0
  155. package/dist/lib/auth/tokens.d.ts +54 -0
  156. package/dist/lib/auth/tokens.js +104 -0
  157. package/dist/lib/auth/types.d.ts +57 -0
  158. package/dist/lib/auth/types.js +1 -0
  159. package/dist/lib/auth/validated-url.d.ts +6 -0
  160. package/dist/lib/auth/validated-url.js +53 -0
  161. package/dist/lib/base-api-command.js +6 -4
  162. package/dist/lib/config.d.ts +44 -4
  163. package/dist/lib/config.js +55 -19
  164. package/oclif.manifest.json +1181 -330
  165. package/package.json +3 -2
  166. package/dist/lib/auth.d.ts +0 -35
  167. package/dist/lib/auth.js +0 -566
@@ -34,7 +34,6 @@ export default class SalesInvoices extends Command {
34
34
  private runCamt053Import;
35
35
  private runNestedImport;
36
36
  private parseFilterValue;
37
- private filterFields;
38
37
  private sanitizeOutput;
39
38
  private getColumns;
40
39
  private getValue;
@@ -1,34 +1,39 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
3
+ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
- const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "create-migrated-invoice", "pdf", "get", "update", "delete", "approve", "match-sales-orders", "validate", "request-approval", "send", "send-resend", "cancel", "copy", "replace", "merge", "create-line-items", "line-items-from-sales-order", "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}`)];
8
+ const NESTED_IMPORTS = ["migrated-invoice", "line-items", "notes", "files"];
9
+ const SUBCOMMANDS = [...["list", "create", "create-migrated-invoice", "pdf", "get", "update", "delete", "payment-derivation", "update-migrated-invoice", "approve", "match-sales-orders", "validate", "request-approval", "send", "send-resend", "cancel", "copy", "replace", "rebuild-journal", "recalculate-payment-status", "merge", "create-line-items", "line-items-from-sales-order", "line-items-from-delivery-note", "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
11
  'list': { method: 'GET', path: '/v1/sales-invoices', summary: 'Get all sales invoices with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/sales-invoices', summary: 'Create a new sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'invoiceEmailCc'] },
13
- 'create-migrated-invoice': { method: 'POST', path: '/v1/sales-invoices/create-migrated-invoice', summary: 'Create a sales invoice with explicit tax codes on line items (for data migration)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lineItems', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'invoiceEmailCc'] },
12
+ 'create': { method: 'POST', path: '/v1/sales-invoices', summary: 'Create a new sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc'] },
13
+ 'create-migrated-invoice': { method: 'POST', path: '/v1/sales-invoices/create-migrated-invoice', summary: 'Create a sales invoice with explicit tax codes on line items (for data migration)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lineItems', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc'] },
14
14
  'pdf': { method: 'GET', path: '/v1/sales-invoices/{id}/pdf', summary: 'Generate a PDF preview of a sales invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
15
  'get': { method: 'GET', path: '/v1/sales-invoices/{id}', summary: 'Get a sales invoice by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
16
- 'update': { method: 'PATCH', path: '/v1/sales-invoices/{id}', summary: 'Update a sales invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'invoiceEmailCc', 'removeFileIds'] },
16
+ 'update': { method: 'PATCH', path: '/v1/sales-invoices/{id}', summary: 'Update a sales invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc', 'removeFileIds'] },
17
17
  'delete': { method: 'DELETE', path: '/v1/sales-invoices/{id}', summary: 'Delete a sales invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
18
- 'approve': { method: 'POST', path: '/v1/sales-invoices/{id}/approve', summary: 'Approve a sales invoice from DRAFT or PENDING_APPROVAL status (generates invoice number and finalizes)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'invoiceEmailCc', 'removeFileIds'] },
19
- 'match-sales-orders': { method: 'POST', path: '/v1/sales-invoices/{id}/match-sales-orders', summary: 'Match this invoice\'s lines to sales-order lines', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
20
- 'validate': { method: 'POST', path: '/v1/sales-invoices/{id}/validate', summary: 'Validate a sales invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'invoiceEmailCc', 'removeFileIds'] },
18
+ 'payment-derivation': { method: 'GET', path: '/v1/sales-invoices/{id}/payment-derivation', summary: 'Explain how a sales invoice\'s paid amount is derived', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
19
+ 'update-migrated-invoice': { method: 'PATCH', path: '/v1/sales-invoices/{id}/update-migrated-invoice', summary: 'Update a migrated sales invoice during migration (before go-live)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lineItems', 'files', 'notes', 'tagIds', 'tagCodes', 'invoiceEmailCc', 'totalAmountByTaxRate'] },
20
+ 'approve': { method: 'POST', path: '/v1/sales-invoices/{id}/approve', summary: 'Approve a sales invoice from DRAFT or PENDING_APPROVAL status (generates invoice number and finalizes)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc', 'removeFileIds'] },
21
+ 'match-sales-orders': { method: 'POST', path: '/v1/sales-invoices/{id}/match-sales-orders', summary: 'Match this invoice\'s lines to sales-order lines and to delivery-note lines', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
+ 'validate': { method: 'POST', path: '/v1/sales-invoices/{id}/validate', summary: 'Validate a sales invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc', 'removeFileIds'] },
21
23
  'request-approval': { method: 'POST', path: '/v1/sales-invoices/{id}/request-approval', summary: 'Request approval for sales invoice (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
22
24
  'send': { method: 'POST', path: '/v1/sales-invoices/{id}/send', summary: 'Mark invoice as sent to customer (APPROVED → SENT)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
23
25
  'send-resend': { method: 'POST', path: '/v1/sales-invoices/{id}/resend', summary: 'Resend a sales invoice to the original delivery recipient or an alternative one', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['emailCc', 'emailBcc'] },
24
26
  'cancel': { method: 'POST', path: '/v1/sales-invoices/{id}/cancel', summary: 'Cancel a sales invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
25
27
  'copy': { method: 'POST', path: '/v1/sales-invoices/{id}/copy', summary: 'Create a copy of a cancelled invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
26
- 'replace': { method: 'POST', path: '/v1/sales-invoices/{id}/replace', summary: 'Replace invoice with corrected version (creates new CORRECTED_INVOICE)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
28
+ 'replace': { method: 'POST', path: '/v1/sales-invoices/{id}/replace', summary: 'Replace invoice (creates a new CORRECTED_INVOICE; a SELF_BILLED_INVOICE is replaced by a new SELF_BILLED_INVOICE)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
29
+ 'rebuild-journal': { method: 'POST', path: '/v1/sales-invoices/{id}/rebuild-journal', summary: 'Re-create the booking journal of a sales invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
30
+ 'recalculate-payment-status': { method: 'POST', path: '/v1/sales-invoices/{id}/recalculate-payment-status', summary: 'Recalculate a sales invoice\'s open amount and payment status', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
27
31
  'merge': { method: 'POST', path: '/v1/sales-invoices/{id}/merge', summary: 'Merge draft invoices into this master', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['sourceIds'] },
28
- 'create-line-items': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items', summary: 'Add a line item to a sales invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts', 'salesOrderLineItems', 'deliveryNoteLineItems'] },
32
+ 'create-line-items': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items', summary: 'Add a line item to a sales invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts', 'salesOrderLineItems', 'deliveryNoteLineItems', 'tagIds', 'tagCodes'] },
29
33
  'line-items-from-sales-order': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items/from-sales-order', summary: 'Add line items to a sales invoice from a sales order', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lines'] },
34
+ 'line-items-from-delivery-note': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items/from-delivery-note', summary: 'Add line items to a sales invoice from a delivery note', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['lines'] },
30
35
  'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-invoices/{invoiceId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['items'] },
31
- 'update-line-items': { method: 'PATCH', path: '/v1/sales-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a sales invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts', 'salesOrderLineItems', 'deliveryNoteLineItems'] },
36
+ 'update-line-items': { method: 'PATCH', path: '/v1/sales-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a sales invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts', 'salesOrderLineItems', 'deliveryNoteLineItems', 'tagIds', 'tagCodes'] },
32
37
  'delete-line-items': { method: 'DELETE', path: '/v1/sales-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
33
38
  'list-notes': { method: 'GET', path: '/v1/sales-invoices/{salesInvoiceId}/notes', summary: 'Get all notes for a sales invoice', pathParams: ['salesInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
34
39
  'create-notes': { method: 'POST', path: '/v1/sales-invoices/{salesInvoiceId}/notes', summary: 'Create a note for a sales invoice', pathParams: ['salesInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
@@ -57,7 +62,7 @@ export default class SalesInvoices extends Command {
57
62
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
58
63
  };
59
64
  static flags = {
60
- context: Flags.string({ char: 'c', description: 'Context to use' }),
65
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
61
66
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
62
67
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
63
68
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -74,7 +79,7 @@ export default class SalesInvoices extends Command {
74
79
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
75
80
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
76
81
  query: Flags.string({ description: 'Additional query params as JSON' }),
77
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
82
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
78
83
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
79
84
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
80
85
  };
@@ -132,9 +137,9 @@ export default class SalesInvoices extends Command {
132
137
  }
133
138
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
134
139
  if (!context)
135
- this.error('No context configured. Run: aderis auth login --new');
136
- if (!context.tokens)
137
- this.error('Not logged in. Run: aderis auth login');
140
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
141
+ if (!hasCredentials(context))
142
+ this.error(missingCredentialsMessage(context));
138
143
  const queryParams = {};
139
144
  if (flags.page)
140
145
  queryParams.page = flags.page;
@@ -146,6 +151,8 @@ export default class SalesInvoices extends Command {
146
151
  queryParams.search = flags.search;
147
152
  if (flags['sort-by'])
148
153
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
154
+ if (flags.fields)
155
+ queryParams.fields = flags.fields;
149
156
  if (flags.query) {
150
157
  try {
151
158
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -348,11 +355,6 @@ export default class SalesInvoices extends Command {
348
355
  output = obj[arrayKeys[0]];
349
356
  }
350
357
  }
351
- // Filter fields if --fields flag is provided
352
- if (flags.fields) {
353
- const fieldList = flags.fields.split(',').map(f => f.trim());
354
- output = this.filterFields(output, fieldList);
355
- }
356
358
  // Sanitize output if requested
357
359
  if (flags.sanitize) {
358
360
  output = this.sanitizeOutput(output);
@@ -377,9 +379,9 @@ export default class SalesInvoices extends Command {
377
379
  async runImport(flags) {
378
380
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
379
381
  if (!context)
380
- this.error('No context configured. Run: aderis auth login --new');
381
- if (!context.tokens)
382
- this.error('Not logged in. Run: aderis auth login');
382
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
383
+ if (!hasCredentials(context))
384
+ this.error(missingCredentialsMessage(context));
383
385
  // Get data from file, --data, or --set flags
384
386
  let items = [];
385
387
  if (flags.file) {
@@ -589,9 +591,9 @@ export default class SalesInvoices extends Command {
589
591
  async runExport(flags) {
590
592
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
591
593
  if (!context)
592
- this.error('No context configured. Run: aderis auth login --new');
593
- if (!context.tokens)
594
- this.error('Not logged in. Run: aderis auth login');
594
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
595
+ if (!hasCredentials(context))
596
+ this.error(missingCredentialsMessage(context));
595
597
  const listEndpoint = ENDPOINTS['list'];
596
598
  if (!listEndpoint)
597
599
  this.error('Export requires a list endpoint');
@@ -607,6 +609,8 @@ export default class SalesInvoices extends Command {
607
609
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
608
610
  if (flags.include)
609
611
  baseParams.include = flags.include;
612
+ if (flags.fields)
613
+ baseParams.fields = flags.fields;
610
614
  if (flags.query) {
611
615
  try {
612
616
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -661,12 +665,7 @@ export default class SalesInvoices extends Command {
661
665
  }
662
666
  }
663
667
  process.stderr.write(' done\n');
664
- // Filter fields if specified
665
668
  let output = allItems;
666
- if (flags.fields) {
667
- const fieldList = flags.fields.split(',').map(f => f.trim());
668
- output = this.filterFields(allItems, fieldList);
669
- }
670
669
  // Sanitize output if requested
671
670
  if (flags.sanitize) {
672
671
  output = this.sanitizeOutput(output);
@@ -683,9 +682,9 @@ export default class SalesInvoices extends Command {
683
682
  async runCamt053Import(flags) {
684
683
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
685
684
  if (!context)
686
- this.error('No context configured. Run: aderis auth login --new');
687
- if (!context.tokens)
688
- this.error('Not logged in. Run: aderis auth login');
685
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
686
+ if (!hasCredentials(context))
687
+ this.error(missingCredentialsMessage(context));
689
688
  if (!flags.file) {
690
689
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
691
690
  }
@@ -773,9 +772,9 @@ export default class SalesInvoices extends Command {
773
772
  }
774
773
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
775
774
  if (!context)
776
- this.error('No context configured. Run: aderis auth login --new');
777
- if (!context.tokens)
778
- this.error('Not logged in. Run: aderis auth login');
775
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
776
+ if (!hasCredentials(context))
777
+ this.error(missingCredentialsMessage(context));
779
778
  // Get data from file, --data, or --set flags
780
779
  let items = [];
781
780
  if (flags.file) {
@@ -995,56 +994,6 @@ export default class SalesInvoices extends Command {
995
994
  return `\$${op}`;
996
995
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
997
996
  }
998
- filterFields(data, fields) {
999
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
1000
- const pick = (obj, keys) => {
1001
- // Group keys by their first segment so sibling sub-paths
1002
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
1003
- // and don't clobber each other on merge.
1004
- const directKeys = new Set();
1005
- const nestedKeys = new Map();
1006
- for (const key of keys) {
1007
- if (key.split('.').some(isUnsafeKey))
1008
- continue;
1009
- if (key.includes('.')) {
1010
- const [first, ...rest] = key.split('.');
1011
- if (isUnsafeKey(first))
1012
- continue;
1013
- const list = nestedKeys.get(first);
1014
- if (list) {
1015
- list.push(rest.join('.'));
1016
- }
1017
- else {
1018
- nestedKeys.set(first, [rest.join('.')]);
1019
- }
1020
- }
1021
- else {
1022
- directKeys.add(key);
1023
- }
1024
- }
1025
- const result = {};
1026
- for (const key of directKeys) {
1027
- if (key in obj)
1028
- result[key] = obj[key];
1029
- }
1030
- for (const [first, restKeys] of nestedKeys) {
1031
- const val = obj[first];
1032
- if (!val || typeof val !== 'object')
1033
- continue;
1034
- if (Array.isArray(val)) {
1035
- result[first] = val.map(item => pick(item, restKeys));
1036
- }
1037
- else {
1038
- result[first] = pick(val, restKeys);
1039
- }
1040
- }
1041
- return result;
1042
- };
1043
- if (Array.isArray(data)) {
1044
- return data.map(item => pick(item, fields));
1045
- }
1046
- return pick(data, fields);
1047
- }
1048
997
  sanitizeOutput(data) {
1049
998
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1050
999
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -34,7 +34,6 @@ export default class SalesOrders extends Command {
34
34
  private runCamt053Import;
35
35
  private runNestedImport;
36
36
  private parseFilterValue;
37
- private filterFields;
38
37
  private sanitizeOutput;
39
38
  private getColumns;
40
39
  private getValue;
@@ -1,6 +1,6 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
3
- import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
3
+ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
4
4
  // Add 'import' action if both create and update exist
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
@@ -9,10 +9,10 @@ const NESTED_IMPORTS = ["line-items", "notes", "files"];
9
9
  const SUBCOMMANDS = [...["list", "create", "create-invoices", "get", "update", "delete", "pdf", "submit", "approve", "accept", "terminate", "cancel", "replace", "merge", "create-invoice", "send", "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
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', 'discounts', 'lineItems'] },
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', 'tagCodes', 'discounts', 'lineItems'] },
13
13
  'create-invoices': { method: 'POST', path: '/v1/sales-orders/create-invoices', summary: 'Create ONE draft invoice from several sales orders (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderIds'] },
14
14
  'get': { method: 'GET', path: '/v1/sales-orders/{id}', summary: 'Get a sales order by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
15
- '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', 'discounts', 'lineItems', 'removeFileIds'] },
15
+ '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', 'tagCodes', 'discounts', 'lineItems', 'removeFileIds'] },
16
16
  'delete': { method: 'DELETE', path: '/v1/sales-orders/{id}', summary: 'Delete a sales order', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
17
17
  '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: [] },
18
18
  'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT or CANCELLED → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
@@ -24,9 +24,9 @@ const ENDPOINTS = {
24
24
  'merge': { method: 'POST', path: '/v1/sales-orders/{id}/merge', summary: 'Merge draft orders into this master', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['sourceIds'] },
25
25
  '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: [] },
26
26
  'send': { method: 'POST', path: '/v1/sales-orders/{id}/send', summary: 'Send a sales order to the customer by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['emailCc', 'emailBcc', 'files'] },
27
- '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: ['discounts'] },
27
+ '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: ['discounts', 'tagIds', 'tagCodes'] },
28
28
  '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'] },
29
- '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: ['discounts'] },
29
+ '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: ['discounts', 'tagIds', 'tagCodes'] },
30
30
  '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: [] },
31
31
  '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: [] },
32
32
  '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: [] },
@@ -55,7 +55,7 @@ export default class SalesOrders extends Command {
55
55
  id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
56
56
  };
57
57
  static flags = {
58
- context: Flags.string({ char: 'c', description: 'Context to use' }),
58
+ context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
59
59
  output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
60
60
  table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
61
61
  csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
@@ -72,7 +72,7 @@ export default class SalesOrders extends Command {
72
72
  'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
73
73
  filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
74
74
  query: Flags.string({ description: 'Additional query params as JSON' }),
75
- fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
75
+ fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
76
76
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
77
77
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
78
78
  };
@@ -130,9 +130,9 @@ export default class SalesOrders extends Command {
130
130
  }
131
131
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
132
132
  if (!context)
133
- this.error('No context configured. Run: aderis auth login --new');
134
- if (!context.tokens)
135
- this.error('Not logged in. Run: aderis auth login');
133
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
134
+ if (!hasCredentials(context))
135
+ this.error(missingCredentialsMessage(context));
136
136
  const queryParams = {};
137
137
  if (flags.page)
138
138
  queryParams.page = flags.page;
@@ -144,6 +144,8 @@ export default class SalesOrders extends Command {
144
144
  queryParams.search = flags.search;
145
145
  if (flags['sort-by'])
146
146
  queryParams.sortBy = normalizeSortBy(flags['sort-by']);
147
+ if (flags.fields)
148
+ queryParams.fields = flags.fields;
147
149
  if (flags.query) {
148
150
  try {
149
151
  Object.assign(queryParams, JSON.parse(flags.query));
@@ -346,11 +348,6 @@ export default class SalesOrders extends Command {
346
348
  output = obj[arrayKeys[0]];
347
349
  }
348
350
  }
349
- // Filter fields if --fields flag is provided
350
- if (flags.fields) {
351
- const fieldList = flags.fields.split(',').map(f => f.trim());
352
- output = this.filterFields(output, fieldList);
353
- }
354
351
  // Sanitize output if requested
355
352
  if (flags.sanitize) {
356
353
  output = this.sanitizeOutput(output);
@@ -375,9 +372,9 @@ export default class SalesOrders extends Command {
375
372
  async runImport(flags) {
376
373
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
377
374
  if (!context)
378
- this.error('No context configured. Run: aderis auth login --new');
379
- if (!context.tokens)
380
- this.error('Not logged in. Run: aderis auth login');
375
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
376
+ if (!hasCredentials(context))
377
+ this.error(missingCredentialsMessage(context));
381
378
  // Get data from file, --data, or --set flags
382
379
  let items = [];
383
380
  if (flags.file) {
@@ -587,9 +584,9 @@ export default class SalesOrders extends Command {
587
584
  async runExport(flags) {
588
585
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
589
586
  if (!context)
590
- this.error('No context configured. Run: aderis auth login --new');
591
- if (!context.tokens)
592
- this.error('Not logged in. Run: aderis auth login');
587
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
588
+ if (!hasCredentials(context))
589
+ this.error(missingCredentialsMessage(context));
593
590
  const listEndpoint = ENDPOINTS['list'];
594
591
  if (!listEndpoint)
595
592
  this.error('Export requires a list endpoint');
@@ -605,6 +602,8 @@ export default class SalesOrders extends Command {
605
602
  baseParams.sortBy = normalizeSortBy(flags['sort-by']);
606
603
  if (flags.include)
607
604
  baseParams.include = flags.include;
605
+ if (flags.fields)
606
+ baseParams.fields = flags.fields;
608
607
  if (flags.query) {
609
608
  try {
610
609
  Object.assign(baseParams, JSON.parse(flags.query));
@@ -659,12 +658,7 @@ export default class SalesOrders extends Command {
659
658
  }
660
659
  }
661
660
  process.stderr.write(' done\n');
662
- // Filter fields if specified
663
661
  let output = allItems;
664
- if (flags.fields) {
665
- const fieldList = flags.fields.split(',').map(f => f.trim());
666
- output = this.filterFields(allItems, fieldList);
667
- }
668
662
  // Sanitize output if requested
669
663
  if (flags.sanitize) {
670
664
  output = this.sanitizeOutput(output);
@@ -681,9 +675,9 @@ export default class SalesOrders extends Command {
681
675
  async runCamt053Import(flags) {
682
676
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
683
677
  if (!context)
684
- this.error('No context configured. Run: aderis auth login --new');
685
- if (!context.tokens)
686
- this.error('Not logged in. Run: aderis auth login');
678
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
679
+ if (!hasCredentials(context))
680
+ this.error(missingCredentialsMessage(context));
687
681
  if (!flags.file) {
688
682
  this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
689
683
  }
@@ -771,9 +765,9 @@ export default class SalesOrders extends Command {
771
765
  }
772
766
  const context = flags.context ? getContext(flags.context) : getCurrentContext();
773
767
  if (!context)
774
- this.error('No context configured. Run: aderis auth login --new');
775
- if (!context.tokens)
776
- this.error('Not logged in. Run: aderis auth login');
768
+ this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
769
+ if (!hasCredentials(context))
770
+ this.error(missingCredentialsMessage(context));
777
771
  // Get data from file, --data, or --set flags
778
772
  let items = [];
779
773
  if (flags.file) {
@@ -993,56 +987,6 @@ export default class SalesOrders extends Command {
993
987
  return `\$${op}`;
994
988
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
995
989
  }
996
- filterFields(data, fields) {
997
- const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
998
- const pick = (obj, keys) => {
999
- // Group keys by their first segment so sibling sub-paths
1000
- // (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
1001
- // and don't clobber each other on merge.
1002
- const directKeys = new Set();
1003
- const nestedKeys = new Map();
1004
- for (const key of keys) {
1005
- if (key.split('.').some(isUnsafeKey))
1006
- continue;
1007
- if (key.includes('.')) {
1008
- const [first, ...rest] = key.split('.');
1009
- if (isUnsafeKey(first))
1010
- continue;
1011
- const list = nestedKeys.get(first);
1012
- if (list) {
1013
- list.push(rest.join('.'));
1014
- }
1015
- else {
1016
- nestedKeys.set(first, [rest.join('.')]);
1017
- }
1018
- }
1019
- else {
1020
- directKeys.add(key);
1021
- }
1022
- }
1023
- const result = {};
1024
- for (const key of directKeys) {
1025
- if (key in obj)
1026
- result[key] = obj[key];
1027
- }
1028
- for (const [first, restKeys] of nestedKeys) {
1029
- const val = obj[first];
1030
- if (!val || typeof val !== 'object')
1031
- continue;
1032
- if (Array.isArray(val)) {
1033
- result[first] = val.map(item => pick(item, restKeys));
1034
- }
1035
- else {
1036
- result[first] = pick(val, restKeys);
1037
- }
1038
- }
1039
- return result;
1040
- };
1041
- if (Array.isArray(data)) {
1042
- return data.map(item => pick(item, fields));
1043
- }
1044
- return pick(data, fields);
1045
- }
1046
990
  sanitizeOutput(data) {
1047
991
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1048
992
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -31,7 +31,6 @@ export default class SavedViews extends Command {
31
31
  private runCamt053Import;
32
32
  private runNestedImport;
33
33
  private parseFilterValue;
34
- private filterFields;
35
34
  private sanitizeOutput;
36
35
  private getColumns;
37
36
  private getValue;