@aderis-erp/cli 1.1.0 → 1.1.2

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 (152) hide show
  1. package/dist/commands/access-groups.d.ts +8 -0
  2. package/dist/commands/access-groups.js +56 -8
  3. package/dist/commands/account-transaction-allocations.d.ts +8 -0
  4. package/dist/commands/account-transaction-allocations.js +52 -5
  5. package/dist/commands/account-transactions.d.ts +8 -0
  6. package/dist/commands/account-transactions.js +55 -7
  7. package/dist/commands/accounts.d.ts +8 -0
  8. package/dist/commands/accounts.js +52 -5
  9. package/dist/commands/agent-platform.d.ts +8 -0
  10. package/dist/commands/agent-platform.js +52 -5
  11. package/dist/commands/agent-runs.d.ts +8 -0
  12. package/dist/commands/agent-runs.js +52 -5
  13. package/dist/commands/analytics.d.ts +8 -0
  14. package/dist/commands/analytics.js +52 -5
  15. package/dist/commands/api-keys.d.ts +8 -0
  16. package/dist/commands/api-keys.js +52 -5
  17. package/dist/commands/async-jobs.d.ts +8 -0
  18. package/dist/commands/async-jobs.js +52 -5
  19. package/dist/commands/audit-log.d.ts +8 -0
  20. package/dist/commands/audit-log.js +52 -5
  21. package/dist/commands/bank-accounts.d.ts +8 -0
  22. package/dist/commands/bank-accounts.js +52 -5
  23. package/dist/commands/bank-integrations.d.ts +8 -0
  24. package/dist/commands/bank-integrations.js +52 -5
  25. package/dist/commands/business-partners.d.ts +9 -0
  26. package/dist/commands/business-partners.js +66 -9
  27. package/dist/commands/chat.d.ts +8 -0
  28. package/dist/commands/chat.js +52 -5
  29. package/dist/commands/credit-card-accounts.d.ts +8 -0
  30. package/dist/commands/credit-card-accounts.js +52 -5
  31. package/dist/commands/delivery-notes.d.ts +8 -0
  32. package/dist/commands/delivery-notes.js +52 -5
  33. package/dist/commands/dunning-configs.d.ts +8 -0
  34. package/dist/commands/dunning-configs.js +52 -5
  35. package/dist/commands/dunning-reminders.d.ts +8 -0
  36. package/dist/commands/dunning-reminders.js +52 -5
  37. package/dist/commands/dunning-runs.d.ts +8 -0
  38. package/dist/commands/dunning-runs.js +52 -5
  39. package/dist/commands/email-transactions.d.ts +8 -0
  40. package/dist/commands/email-transactions.js +52 -5
  41. package/dist/commands/employees.d.ts +8 -0
  42. package/dist/commands/employees.js +52 -5
  43. package/dist/commands/entity-configurations.d.ts +8 -0
  44. package/dist/commands/entity-configurations.js +52 -5
  45. package/dist/commands/event-processor-mappings.d.ts +8 -0
  46. package/dist/commands/event-processor-mappings.js +52 -5
  47. package/dist/commands/files.d.ts +8 -0
  48. package/dist/commands/files.js +53 -6
  49. package/dist/commands/health.d.ts +8 -0
  50. package/dist/commands/health.js +52 -5
  51. package/dist/commands/imports.d.ts +8 -0
  52. package/dist/commands/imports.js +53 -8
  53. package/dist/commands/inbox.d.ts +8 -0
  54. package/dist/commands/inbox.js +53 -7
  55. package/dist/commands/integration-identities.d.ts +8 -0
  56. package/dist/commands/integration-identities.js +52 -5
  57. package/dist/commands/integrations.d.ts +8 -0
  58. package/dist/commands/integrations.js +52 -5
  59. package/dist/commands/item-categories.d.ts +8 -0
  60. package/dist/commands/item-categories.js +52 -5
  61. package/dist/commands/items.d.ts +8 -0
  62. package/dist/commands/items.js +52 -5
  63. package/dist/commands/journal-lines.d.ts +8 -0
  64. package/dist/commands/journal-lines.js +52 -5
  65. package/dist/commands/journals.d.ts +8 -0
  66. package/dist/commands/journals.js +52 -5
  67. package/dist/commands/ledgers.d.ts +8 -0
  68. package/dist/commands/ledgers.js +57 -9
  69. package/dist/commands/my-account.d.ts +8 -0
  70. package/dist/commands/my-account.js +52 -5
  71. package/dist/commands/office-locations.d.ts +8 -0
  72. package/dist/commands/office-locations.js +52 -5
  73. package/dist/commands/payment-methods.d.ts +8 -0
  74. package/dist/commands/payment-methods.js +52 -5
  75. package/dist/commands/payment-runs.d.ts +8 -0
  76. package/dist/commands/payment-runs.js +54 -6
  77. package/dist/commands/payment-terms.d.ts +8 -0
  78. package/dist/commands/payment-terms.js +52 -5
  79. package/dist/commands/payments.d.ts +8 -0
  80. package/dist/commands/payments.js +52 -5
  81. package/dist/commands/payroll-runs.d.ts +8 -0
  82. package/dist/commands/payroll-runs.js +52 -5
  83. package/dist/commands/permissions.d.ts +8 -0
  84. package/dist/commands/permissions.js +52 -5
  85. package/dist/commands/price-lists.d.ts +8 -0
  86. package/dist/commands/price-lists.js +52 -5
  87. package/dist/commands/processor-mappings.d.ts +8 -0
  88. package/dist/commands/processor-mappings.js +52 -5
  89. package/dist/commands/processors.d.ts +8 -0
  90. package/dist/commands/processors.js +52 -5
  91. package/dist/commands/purchase-order-types.d.ts +8 -0
  92. package/dist/commands/purchase-order-types.js +52 -5
  93. package/dist/commands/purchase-orders.d.ts +8 -0
  94. package/dist/commands/purchase-orders.js +52 -5
  95. package/dist/commands/reports.d.ts +8 -0
  96. package/dist/commands/reports.js +52 -5
  97. package/dist/commands/roles.d.ts +8 -0
  98. package/dist/commands/roles.js +52 -5
  99. package/dist/commands/salary-types.d.ts +8 -0
  100. package/dist/commands/salary-types.js +52 -5
  101. package/dist/commands/sales-invoices.d.ts +10 -0
  102. package/dist/commands/sales-invoices.js +62 -6
  103. package/dist/commands/sales-orders.d.ts +8 -0
  104. package/dist/commands/sales-orders.js +52 -5
  105. package/dist/commands/saved-views.d.ts +8 -0
  106. package/dist/commands/saved-views.js +52 -5
  107. package/dist/commands/scheduled-processor-mappings.d.ts +8 -0
  108. package/dist/commands/scheduled-processor-mappings.js +52 -5
  109. package/dist/commands/schedules.d.ts +8 -0
  110. package/dist/commands/schedules.js +52 -5
  111. package/dist/commands/support/end.d.ts +9 -0
  112. package/dist/commands/support/end.js +29 -0
  113. package/dist/commands/support/start.d.ts +14 -0
  114. package/dist/commands/support/start.js +50 -0
  115. package/dist/commands/support/status.d.ts +10 -0
  116. package/dist/commands/support/status.js +45 -0
  117. package/dist/commands/support/tenants.d.ts +10 -0
  118. package/dist/commands/support/tenants.js +39 -0
  119. package/dist/commands/tags.d.ts +8 -0
  120. package/dist/commands/tags.js +56 -8
  121. package/dist/commands/tax-codes.d.ts +8 -0
  122. package/dist/commands/tax-codes.js +52 -5
  123. package/dist/commands/tax-matrix.d.ts +8 -0
  124. package/dist/commands/tax-matrix.js +52 -5
  125. package/dist/commands/templates.d.ts +8 -0
  126. package/dist/commands/templates.js +52 -5
  127. package/dist/commands/tenants.d.ts +8 -0
  128. package/dist/commands/tenants.js +52 -5
  129. package/dist/commands/tools.d.ts +8 -0
  130. package/dist/commands/tools.js +52 -5
  131. package/dist/commands/users.d.ts +8 -0
  132. package/dist/commands/users.js +52 -5
  133. package/dist/commands/vat-declarations.d.ts +8 -0
  134. package/dist/commands/vat-declarations.js +52 -5
  135. package/dist/commands/vendor-invoices.d.ts +9 -0
  136. package/dist/commands/vendor-invoices.js +60 -8
  137. package/dist/commands/webhooks.d.ts +8 -0
  138. package/dist/commands/webhooks.js +52 -5
  139. package/dist/commands/workflows.d.ts +8 -0
  140. package/dist/commands/workflows.js +52 -5
  141. package/dist/lib/auth/constants.d.ts +12 -0
  142. package/dist/lib/auth/constants.js +12 -0
  143. package/dist/lib/auth/index.d.ts +11 -1
  144. package/dist/lib/auth/index.js +90 -2
  145. package/dist/lib/auth/keycloak-provider.d.ts +22 -0
  146. package/dist/lib/auth/keycloak-provider.js +108 -3
  147. package/dist/lib/config.d.ts +18 -0
  148. package/dist/lib/config.js +10 -0
  149. package/oclif.manifest.json +640 -216
  150. package/package.json +4 -1
  151. package/dist/commands/tag-configurations.d.ts +0 -39
  152. package/dist/commands/tag-configurations.js +0 -1155
@@ -26,6 +26,7 @@ export default class BankIntegrations extends Command {
26
26
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
27
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
28
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
30
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
31
  };
31
32
  run(): Promise<void>;
@@ -34,6 +35,13 @@ export default class BankIntegrations extends Command {
34
35
  private runCamt053Import;
35
36
  private runNestedImport;
36
37
  private parseFilterValue;
38
+ /** The page line under a table: which page, how many, and any sums the API returned. */
39
+ private printPageSummary;
40
+ /**
41
+ * The API caps how many records a page may hold. Asking for more and getting
42
+ * fewer looks like "that is everything" unless it is said out loud.
43
+ */
44
+ private warnShortPage;
37
45
  private sanitizeOutput;
38
46
  private getColumns;
39
47
  private getValue;
@@ -63,6 +63,7 @@ export default class BankIntegrations extends Command {
63
63
  query: Flags.string({ description: 'Additional query params as JSON' }),
64
64
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
65
65
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
66
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
66
67
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
67
68
  };
68
69
  async run() {
@@ -355,9 +356,15 @@ export default class BankIntegrations extends Command {
355
356
  }
356
357
  catch { /* keep as string */ }
357
358
  }
358
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
359
+ // A paginated answer is { data, meta, links, totals }. The records are
360
+ // unwrapped for output, but what the API said about the page is kept:
361
+ // dropping it silently let a script read one page as the whole result
362
+ // (AderisERP/product#61).
363
+ let envelope;
359
364
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
360
- output = output.data;
365
+ const page = output;
366
+ envelope = page;
367
+ output = page.data;
361
368
  }
362
369
  // For table/csv on non-array responses, auto-extract the primary array
363
370
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -374,12 +381,24 @@ export default class BankIntegrations extends Command {
374
381
  output = this.sanitizeOutput(output);
375
382
  }
376
383
  const fieldList = flags.fields?.split(',').map(f => f.trim());
377
- if (flags.csv || flags.output === 'csv')
384
+ if (envelope)
385
+ this.warnShortPage(envelope, flags.limit);
386
+ if (flags.csv || flags.output === 'csv') {
378
387
  this.printCsv(output, fieldList);
379
- else if (flags.table || flags.output === 'table')
388
+ }
389
+ else if (flags.table || flags.output === 'table') {
380
390
  this.printTable(output, fieldList);
381
- else
391
+ if (envelope)
392
+ this.printPageSummary(envelope);
393
+ }
394
+ else if (envelope && flags.meta) {
395
+ // The whole envelope, for a caller that asked for it; the default stays
396
+ // a bare array so existing scripts are unaffected.
397
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
398
+ }
399
+ else {
382
400
  this.log(JSON.stringify(output, null, 2));
401
+ }
383
402
  }
384
403
  catch (error) {
385
404
  if (error instanceof ApiError) {
@@ -1008,6 +1027,34 @@ export default class BankIntegrations extends Command {
1008
1027
  return `\$${op}`;
1009
1028
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
1010
1029
  }
1030
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1031
+ printPageSummary(envelope) {
1032
+ const meta = envelope.meta;
1033
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1034
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1035
+ this.log(`
1036
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1037
+ }
1038
+ for (const total of envelope.totals ?? []) {
1039
+ const { currency, ...sums } = total;
1040
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1041
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1042
+ }
1043
+ }
1044
+ /**
1045
+ * The API caps how many records a page may hold. Asking for more and getting
1046
+ * fewer looks like "that is everything" unless it is said out loud.
1047
+ */
1048
+ warnShortPage(envelope, requestedLimit) {
1049
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1050
+ const perPage = envelope.meta?.itemsPerPage;
1051
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1052
+ return;
1053
+ const pages = envelope.meta?.totalPages;
1054
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1055
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1056
+ `);
1057
+ }
1011
1058
  sanitizeOutput(data) {
1012
1059
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1013
1060
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -17,6 +17,7 @@ export default class BusinessPartners extends Command {
17
17
  'note-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  'file-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'business-partner-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
21
  page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
22
  limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
23
  include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -26,6 +27,7 @@ export default class BusinessPartners extends Command {
26
27
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
28
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
29
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
31
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
32
  };
31
33
  run(): Promise<void>;
@@ -34,6 +36,13 @@ export default class BusinessPartners extends Command {
34
36
  private runCamt053Import;
35
37
  private runNestedImport;
36
38
  private parseFilterValue;
39
+ /** The page line under a table: which page, how many, and any sums the API returned. */
40
+ private printPageSummary;
41
+ /**
42
+ * The API caps how many records a page may hold. Asking for more and getting
43
+ * fewer looks like "that is everything" unless it is said out loud.
44
+ */
45
+ private warnShortPage;
37
46
  private sanitizeOutput;
38
47
  private getColumns;
39
48
  private getValue;
@@ -5,14 +5,14 @@ import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCred
5
5
  const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
- const NESTED_IMPORTS = ["notes", "files", "contacts", "contacts-notes"];
9
- const SUBCOMMANDS = [...["list", "create", "match-candidates", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
8
+ const NESTED_IMPORTS = ["notes", "files", "contacts", "contacts-notes", "beneficiary-accounts"];
9
+ const SUBCOMMANDS = [...["list", "create", "match-candidates", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes", "list-beneficiary-accounts", "create-beneficiary-accounts", "get-beneficiary-accounts", "update-beneficiary-accounts", "delete-beneficiary-accounts", "beneficiary-accounts-approve", "beneficiary-accounts-retire", "beneficiary-accounts-reactivate", "beneficiary-accounts-default"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
11
  'list': { method: 'GET', path: '/v1/business-partners', summary: 'Get all business partners with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
- 'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
12
+ 'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
13
13
  'match-candidates': { method: 'GET', path: '/v1/business-partners/match-candidates', summary: 'Rank the business partners whose names resemble a bank counterparty text', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
14
14
  'get': { method: 'GET', path: '/v1/business-partners/{id}', summary: 'Get a business partner by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
15
- 'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
15
+ 'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'dunningRecipientCc', 'dunningRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
16
16
  'delete': { method: 'DELETE', path: '/v1/business-partners/{id}', summary: 'Delete a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
17
17
  'merge': { method: 'POST', path: '/v1/business-partners/{id}/merge', summary: 'Merge a business partner into another', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
18
18
  'activate': { method: 'POST', path: '/v1/business-partners/{id}/activate', summary: 'Activate a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
@@ -40,6 +40,15 @@ const ENDPOINTS = {
40
40
  'get-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Get a specific note for a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
41
41
  'update-contacts-notes': { method: 'PATCH', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Update a note for a contact', pathParams: ['contactId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
42
42
  'delete-contacts-notes': { method: 'DELETE', path: '/v1/business-partners/contacts/{contactId}/notes/{noteId}', summary: 'Delete a note from a contact', pathParams: ['contactId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
+ 'list-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'List the beneficiary bank accounts of a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
44
+ 'create-beneficiary-accounts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts', summary: 'Store a new beneficiary bank account (status NEW)', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
45
+ 'get-beneficiary-accounts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Get one beneficiary bank account of a business partner', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
46
+ 'update-beneficiary-accounts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Edit a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
47
+ 'delete-beneficiary-accounts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}', summary: 'Delete a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
48
+ 'beneficiary-accounts-approve': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/approve', summary: 'Approve a beneficiary bank account', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
49
+ 'beneficiary-accounts-retire': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/retire', summary: 'Retire a beneficiary bank account; clears its default flag', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
50
+ 'beneficiary-accounts-reactivate': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/reactivate', summary: 'Reactivate a retired account: NEW again, both approvals cleared', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
51
+ 'beneficiary-accounts-default': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/beneficiary-accounts/{id}/default', summary: 'Make this account the partner default; the previous one is unset', pathParams: ['businessPartnerId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
43
52
  };
44
53
  export default class BusinessPartners extends Command {
45
54
  static description = 'Manage business partners';
@@ -66,6 +75,7 @@ export default class BusinessPartners extends Command {
66
75
  'note-id': Flags.string({ description: 'Path param: noteId' }),
67
76
  'file-id': Flags.string({ description: 'Path param: fileId' }),
68
77
  'business-partner-id': Flags.string({ description: 'Path param: businessPartnerId' }),
78
+ 'id': Flags.string({ description: 'Path param: id' }),
69
79
  page: Flags.string({ description: 'Page number' }),
70
80
  limit: Flags.string({ description: 'Items per page' }),
71
81
  include: Flags.string({ description: 'Relations to include' }),
@@ -75,6 +85,7 @@ export default class BusinessPartners extends Command {
75
85
  query: Flags.string({ description: 'Additional query params as JSON' }),
76
86
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
77
87
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
88
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
78
89
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
79
90
  };
80
91
  async run() {
@@ -367,9 +378,15 @@ export default class BusinessPartners extends Command {
367
378
  }
368
379
  catch { /* keep as string */ }
369
380
  }
370
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
381
+ // A paginated answer is { data, meta, links, totals }. The records are
382
+ // unwrapped for output, but what the API said about the page is kept:
383
+ // dropping it silently let a script read one page as the whole result
384
+ // (AderisERP/product#61).
385
+ let envelope;
371
386
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
372
- output = output.data;
387
+ const page = output;
388
+ envelope = page;
389
+ output = page.data;
373
390
  }
374
391
  // For table/csv on non-array responses, auto-extract the primary array
375
392
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -386,12 +403,24 @@ export default class BusinessPartners extends Command {
386
403
  output = this.sanitizeOutput(output);
387
404
  }
388
405
  const fieldList = flags.fields?.split(',').map(f => f.trim());
389
- if (flags.csv || flags.output === 'csv')
406
+ if (envelope)
407
+ this.warnShortPage(envelope, flags.limit);
408
+ if (flags.csv || flags.output === 'csv') {
390
409
  this.printCsv(output, fieldList);
391
- else if (flags.table || flags.output === 'table')
410
+ }
411
+ else if (flags.table || flags.output === 'table') {
392
412
  this.printTable(output, fieldList);
393
- else
413
+ if (envelope)
414
+ this.printPageSummary(envelope);
415
+ }
416
+ else if (envelope && flags.meta) {
417
+ // The whole envelope, for a caller that asked for it; the default stays
418
+ // a bare array so existing scripts are unaffected.
419
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
420
+ }
421
+ else {
394
422
  this.log(JSON.stringify(output, null, 2));
423
+ }
395
424
  }
396
425
  catch (error) {
397
426
  if (error instanceof ApiError) {
@@ -1020,6 +1049,34 @@ export default class BusinessPartners extends Command {
1020
1049
  return `\$${op}`;
1021
1050
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
1022
1051
  }
1052
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1053
+ printPageSummary(envelope) {
1054
+ const meta = envelope.meta;
1055
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1056
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1057
+ this.log(`
1058
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1059
+ }
1060
+ for (const total of envelope.totals ?? []) {
1061
+ const { currency, ...sums } = total;
1062
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1063
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1064
+ }
1065
+ }
1066
+ /**
1067
+ * The API caps how many records a page may hold. Asking for more and getting
1068
+ * fewer looks like "that is everything" unless it is said out loud.
1069
+ */
1070
+ warnShortPage(envelope, requestedLimit) {
1071
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1072
+ const perPage = envelope.meta?.itemsPerPage;
1073
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1074
+ return;
1075
+ const pages = envelope.meta?.totalPages;
1076
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1077
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1078
+ `);
1079
+ }
1023
1080
  sanitizeOutput(data) {
1024
1081
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1025
1082
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -23,6 +23,7 @@ export default class Chat extends Command {
23
23
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
24
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
25
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
27
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
28
  };
28
29
  run(): Promise<void>;
@@ -31,6 +32,13 @@ export default class Chat extends Command {
31
32
  private runCamt053Import;
32
33
  private runNestedImport;
33
34
  private parseFilterValue;
35
+ /** The page line under a table: which page, how many, and any sums the API returned. */
36
+ private printPageSummary;
37
+ /**
38
+ * The API caps how many records a page may hold. Asking for more and getting
39
+ * fewer looks like "that is everything" unless it is said out loud.
40
+ */
41
+ private warnShortPage;
34
42
  private sanitizeOutput;
35
43
  private getColumns;
36
44
  private getValue;
@@ -45,6 +45,7 @@ export default class Chat extends Command {
45
45
  query: Flags.string({ description: 'Additional query params as JSON' }),
46
46
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
47
47
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
48
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
48
49
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
49
50
  };
50
51
  async run() {
@@ -337,9 +338,15 @@ export default class Chat extends Command {
337
338
  }
338
339
  catch { /* keep as string */ }
339
340
  }
340
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
341
+ // A paginated answer is { data, meta, links, totals }. The records are
342
+ // unwrapped for output, but what the API said about the page is kept:
343
+ // dropping it silently let a script read one page as the whole result
344
+ // (AderisERP/product#61).
345
+ let envelope;
341
346
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
342
- output = output.data;
347
+ const page = output;
348
+ envelope = page;
349
+ output = page.data;
343
350
  }
344
351
  // For table/csv on non-array responses, auto-extract the primary array
345
352
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -356,12 +363,24 @@ export default class Chat extends Command {
356
363
  output = this.sanitizeOutput(output);
357
364
  }
358
365
  const fieldList = flags.fields?.split(',').map(f => f.trim());
359
- if (flags.csv || flags.output === 'csv')
366
+ if (envelope)
367
+ this.warnShortPage(envelope, flags.limit);
368
+ if (flags.csv || flags.output === 'csv') {
360
369
  this.printCsv(output, fieldList);
361
- else if (flags.table || flags.output === 'table')
370
+ }
371
+ else if (flags.table || flags.output === 'table') {
362
372
  this.printTable(output, fieldList);
363
- else
373
+ if (envelope)
374
+ this.printPageSummary(envelope);
375
+ }
376
+ else if (envelope && flags.meta) {
377
+ // The whole envelope, for a caller that asked for it; the default stays
378
+ // a bare array so existing scripts are unaffected.
379
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
380
+ }
381
+ else {
364
382
  this.log(JSON.stringify(output, null, 2));
383
+ }
365
384
  }
366
385
  catch (error) {
367
386
  if (error instanceof ApiError) {
@@ -990,6 +1009,34 @@ export default class Chat extends Command {
990
1009
  return `\$${op}`;
991
1010
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
992
1011
  }
1012
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1013
+ printPageSummary(envelope) {
1014
+ const meta = envelope.meta;
1015
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1016
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1017
+ this.log(`
1018
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1019
+ }
1020
+ for (const total of envelope.totals ?? []) {
1021
+ const { currency, ...sums } = total;
1022
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1023
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1024
+ }
1025
+ }
1026
+ /**
1027
+ * The API caps how many records a page may hold. Asking for more and getting
1028
+ * fewer looks like "that is everything" unless it is said out loud.
1029
+ */
1030
+ warnShortPage(envelope, requestedLimit) {
1031
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1032
+ const perPage = envelope.meta?.itemsPerPage;
1033
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1034
+ return;
1035
+ const pages = envelope.meta?.totalPages;
1036
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1037
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1038
+ `);
1039
+ }
993
1040
  sanitizeOutput(data) {
994
1041
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
995
1042
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -26,6 +26,7 @@ export default class CreditCardAccounts extends Command {
26
26
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
27
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
28
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
30
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
31
  };
31
32
  run(): Promise<void>;
@@ -34,6 +35,13 @@ export default class CreditCardAccounts extends Command {
34
35
  private runCamt053Import;
35
36
  private runNestedImport;
36
37
  private parseFilterValue;
38
+ /** The page line under a table: which page, how many, and any sums the API returned. */
39
+ private printPageSummary;
40
+ /**
41
+ * The API caps how many records a page may hold. Asking for more and getting
42
+ * fewer looks like "that is everything" unless it is said out loud.
43
+ */
44
+ private warnShortPage;
37
45
  private sanitizeOutput;
38
46
  private getColumns;
39
47
  private getValue;
@@ -63,6 +63,7 @@ export default class CreditCardAccounts extends Command {
63
63
  query: Flags.string({ description: 'Additional query params as JSON' }),
64
64
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
65
65
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
66
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
66
67
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
67
68
  };
68
69
  async run() {
@@ -355,9 +356,15 @@ export default class CreditCardAccounts extends Command {
355
356
  }
356
357
  catch { /* keep as string */ }
357
358
  }
358
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
359
+ // A paginated answer is { data, meta, links, totals }. The records are
360
+ // unwrapped for output, but what the API said about the page is kept:
361
+ // dropping it silently let a script read one page as the whole result
362
+ // (AderisERP/product#61).
363
+ let envelope;
359
364
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
360
- output = output.data;
365
+ const page = output;
366
+ envelope = page;
367
+ output = page.data;
361
368
  }
362
369
  // For table/csv on non-array responses, auto-extract the primary array
363
370
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -374,12 +381,24 @@ export default class CreditCardAccounts extends Command {
374
381
  output = this.sanitizeOutput(output);
375
382
  }
376
383
  const fieldList = flags.fields?.split(',').map(f => f.trim());
377
- if (flags.csv || flags.output === 'csv')
384
+ if (envelope)
385
+ this.warnShortPage(envelope, flags.limit);
386
+ if (flags.csv || flags.output === 'csv') {
378
387
  this.printCsv(output, fieldList);
379
- else if (flags.table || flags.output === 'table')
388
+ }
389
+ else if (flags.table || flags.output === 'table') {
380
390
  this.printTable(output, fieldList);
381
- else
391
+ if (envelope)
392
+ this.printPageSummary(envelope);
393
+ }
394
+ else if (envelope && flags.meta) {
395
+ // The whole envelope, for a caller that asked for it; the default stays
396
+ // a bare array so existing scripts are unaffected.
397
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
398
+ }
399
+ else {
382
400
  this.log(JSON.stringify(output, null, 2));
401
+ }
383
402
  }
384
403
  catch (error) {
385
404
  if (error instanceof ApiError) {
@@ -1008,6 +1027,34 @@ export default class CreditCardAccounts extends Command {
1008
1027
  return `\$${op}`;
1009
1028
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
1010
1029
  }
1030
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1031
+ printPageSummary(envelope) {
1032
+ const meta = envelope.meta;
1033
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1034
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1035
+ this.log(`
1036
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1037
+ }
1038
+ for (const total of envelope.totals ?? []) {
1039
+ const { currency, ...sums } = total;
1040
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1041
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1042
+ }
1043
+ }
1044
+ /**
1045
+ * The API caps how many records a page may hold. Asking for more and getting
1046
+ * fewer looks like "that is everything" unless it is said out loud.
1047
+ */
1048
+ warnShortPage(envelope, requestedLimit) {
1049
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1050
+ const perPage = envelope.meta?.itemsPerPage;
1051
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1052
+ return;
1053
+ const pages = envelope.meta?.totalPages;
1054
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1055
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1056
+ `);
1057
+ }
1011
1058
  sanitizeOutput(data) {
1012
1059
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1013
1060
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -26,6 +26,7 @@ export default class DeliveryNotes extends Command {
26
26
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
27
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
28
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
30
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
31
  };
31
32
  run(): Promise<void>;
@@ -34,6 +35,13 @@ export default class DeliveryNotes extends Command {
34
35
  private runCamt053Import;
35
36
  private runNestedImport;
36
37
  private parseFilterValue;
38
+ /** The page line under a table: which page, how many, and any sums the API returned. */
39
+ private printPageSummary;
40
+ /**
41
+ * The API caps how many records a page may hold. Asking for more and getting
42
+ * fewer looks like "that is everything" unless it is said out loud.
43
+ */
44
+ private warnShortPage;
37
45
  private sanitizeOutput;
38
46
  private getColumns;
39
47
  private getValue;
@@ -76,6 +76,7 @@ export default class DeliveryNotes extends Command {
76
76
  query: Flags.string({ description: 'Additional query params as JSON' }),
77
77
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
78
78
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
79
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
79
80
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
80
81
  };
81
82
  async run() {
@@ -368,9 +369,15 @@ export default class DeliveryNotes extends Command {
368
369
  }
369
370
  catch { /* keep as string */ }
370
371
  }
371
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
372
+ // A paginated answer is { data, meta, links, totals }. The records are
373
+ // unwrapped for output, but what the API said about the page is kept:
374
+ // dropping it silently let a script read one page as the whole result
375
+ // (AderisERP/product#61).
376
+ let envelope;
372
377
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
373
- output = output.data;
378
+ const page = output;
379
+ envelope = page;
380
+ output = page.data;
374
381
  }
375
382
  // For table/csv on non-array responses, auto-extract the primary array
376
383
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -387,12 +394,24 @@ export default class DeliveryNotes extends Command {
387
394
  output = this.sanitizeOutput(output);
388
395
  }
389
396
  const fieldList = flags.fields?.split(',').map(f => f.trim());
390
- if (flags.csv || flags.output === 'csv')
397
+ if (envelope)
398
+ this.warnShortPage(envelope, flags.limit);
399
+ if (flags.csv || flags.output === 'csv') {
391
400
  this.printCsv(output, fieldList);
392
- else if (flags.table || flags.output === 'table')
401
+ }
402
+ else if (flags.table || flags.output === 'table') {
393
403
  this.printTable(output, fieldList);
394
- else
404
+ if (envelope)
405
+ this.printPageSummary(envelope);
406
+ }
407
+ else if (envelope && flags.meta) {
408
+ // The whole envelope, for a caller that asked for it; the default stays
409
+ // a bare array so existing scripts are unaffected.
410
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
411
+ }
412
+ else {
395
413
  this.log(JSON.stringify(output, null, 2));
414
+ }
396
415
  }
397
416
  catch (error) {
398
417
  if (error instanceof ApiError) {
@@ -1021,6 +1040,34 @@ export default class DeliveryNotes extends Command {
1021
1040
  return `\$${op}`;
1022
1041
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
1023
1042
  }
1043
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1044
+ printPageSummary(envelope) {
1045
+ const meta = envelope.meta;
1046
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1047
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1048
+ this.log(`
1049
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1050
+ }
1051
+ for (const total of envelope.totals ?? []) {
1052
+ const { currency, ...sums } = total;
1053
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1054
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1055
+ }
1056
+ }
1057
+ /**
1058
+ * The API caps how many records a page may hold. Asking for more and getting
1059
+ * fewer looks like "that is everything" unless it is said out loud.
1060
+ */
1061
+ warnShortPage(envelope, requestedLimit) {
1062
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1063
+ const perPage = envelope.meta?.itemsPerPage;
1064
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1065
+ return;
1066
+ const pages = envelope.meta?.totalPages;
1067
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1068
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1069
+ `);
1070
+ }
1024
1071
  sanitizeOutput(data) {
1025
1072
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1026
1073
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;