@aderis-erp/cli 1.1.1 → 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 (134) hide show
  1. package/dist/commands/access-groups.d.ts +8 -0
  2. package/dist/commands/access-groups.js +54 -6
  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 +64 -7
  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 +52 -5
  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 +52 -5
  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/tags.d.ts +8 -0
  112. package/dist/commands/tags.js +52 -5
  113. package/dist/commands/tax-codes.d.ts +8 -0
  114. package/dist/commands/tax-codes.js +52 -5
  115. package/dist/commands/tax-matrix.d.ts +8 -0
  116. package/dist/commands/tax-matrix.js +52 -5
  117. package/dist/commands/templates.d.ts +8 -0
  118. package/dist/commands/templates.js +52 -5
  119. package/dist/commands/tenants.d.ts +8 -0
  120. package/dist/commands/tenants.js +52 -5
  121. package/dist/commands/tools.d.ts +8 -0
  122. package/dist/commands/tools.js +52 -5
  123. package/dist/commands/users.d.ts +8 -0
  124. package/dist/commands/users.js +52 -5
  125. package/dist/commands/vat-declarations.d.ts +8 -0
  126. package/dist/commands/vat-declarations.js +52 -5
  127. package/dist/commands/vendor-invoices.d.ts +9 -0
  128. package/dist/commands/vendor-invoices.js +60 -8
  129. package/dist/commands/webhooks.d.ts +8 -0
  130. package/dist/commands/webhooks.js +52 -5
  131. package/dist/commands/workflows.d.ts +8 -0
  132. package/dist/commands/workflows.js +52 -5
  133. package/oclif.manifest.json +433 -10
  134. package/package.json +1 -1
@@ -45,6 +45,7 @@ export default class SalaryTypes 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 SalaryTypes 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 SalaryTypes 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 SalaryTypes 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;
@@ -14,9 +14,11 @@ export default class SalesInvoices extends Command {
14
14
  data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'offset-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
18
  'line-item-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  'note-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
20
  'file-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'payment-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
22
  page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
23
  limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
24
  include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -26,6 +28,7 @@ export default class SalesInvoices extends Command {
26
28
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
29
  fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
30
  sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
31
+ meta: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
32
  'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
33
  };
31
34
  run(): Promise<void>;
@@ -34,6 +37,13 @@ export default class SalesInvoices extends Command {
34
37
  private runCamt053Import;
35
38
  private runNestedImport;
36
39
  private parseFilterValue;
40
+ /** The page line under a table: which page, how many, and any sums the API returned. */
41
+ private printPageSummary;
42
+ /**
43
+ * The API caps how many records a page may hold. Asking for more and getting
44
+ * fewer looks like "that is everything" unless it is said out loud.
45
+ */
46
+ private warnShortPage;
37
47
  private sanitizeOutput;
38
48
  private getColumns;
39
49
  private getValue;
@@ -6,8 +6,11 @@ const HAS_IMPORT = true;
6
6
  const HAS_EXPORT = true;
7
7
  const HAS_CAMT053 = false;
8
8
  const NESTED_IMPORTS = ["migrated-invoice", "line-items", "notes", "files"];
9
- const SUBCOMMANDS = [...["list", "create", "create-migrated-invoice", "get-pdf", "create-pdf", "get", "update", "delete", "payment-derivation", "update-migrated-invoice", "approve", "match", "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}`)];
9
+ const SUBCOMMANDS = [...["create-offsets", "delete-offsets", "offset-candidates", "list", "create", "create-migrated-invoice", "get-pdf", "create-pdf", "get", "update", "delete", "payment-derivation", "update-migrated-invoice", "approve", "match", "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", "line-items-from-sales-invoice", "credit-notes", "list-refund-account-options", "update-refund-payments-beneficiary-account"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
10
10
  const ENDPOINTS = {
11
+ 'create-offsets': { method: 'POST', path: '/v1/sales-invoices/{salesInvoiceId}/offsets', summary: 'Deduct a prepayment or credit note from a sales invoice', pathParams: ['salesInvoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
+ 'delete-offsets': { method: 'DELETE', path: '/v1/sales-invoices/{salesInvoiceId}/offsets/{offsetId}', summary: 'Remove a deduction of a prepayment or credit note from a sales invoice', pathParams: ['salesInvoiceId', 'offsetId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
13
+ 'offset-candidates': { method: 'GET', path: '/v1/sales-invoices/{salesInvoiceId}/offset-candidates', summary: 'List the prepayments and credit notes that can be deducted from a sales invoice', pathParams: ['salesInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
11
14
  'list': { method: 'GET', path: '/v1/sales-invoices', summary: 'Get all sales invoices with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
12
15
  'create': { method: 'POST', path: '/v1/sales-invoices', summary: 'Create a new sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts', 'lineItems', 'discounts', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc'] },
13
16
  '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, hasBinaryVariant: false, bodyArrayFields: ['lineItems', 'files', 'notes', 'totalAmountByTaxRate', 'tagIds', 'tagCodes', 'invoiceEmailCc'] },
@@ -47,6 +50,10 @@ const ENDPOINTS = {
47
50
  'update-files': { method: 'PATCH', path: '/v1/sales-invoices/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a sales invoice', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
48
51
  'delete-files': { method: 'DELETE', path: '/v1/sales-invoices/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a sales invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
49
52
  'files-download': { method: 'GET', path: '/v1/sales-invoices/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a sales invoice', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
53
+ 'line-items-from-sales-invoice': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/line-items/from-sales-invoice', summary: 'Add line items to a sales credit note from a sales invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'] },
54
+ 'credit-notes': { method: 'POST', path: '/v1/sales-invoices/{invoiceId}/credit-notes', summary: 'Create a sales credit note from a sales invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'] },
55
+ 'list-refund-account-options': { method: 'GET', path: '/v1/sales-invoices/{salesInvoiceId}/refund-account-options', summary: 'List the accounts the refund of a sales credit note can be paid to', pathParams: ['salesInvoiceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
56
+ 'update-refund-payments-beneficiary-account': { method: 'PUT', path: '/v1/sales-invoices/{salesInvoiceId}/refund-payments/{paymentId}/beneficiary-account', summary: 'Choose the account a refund payment pays to', pathParams: ['salesInvoiceId', 'paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
50
57
  };
51
58
  export default class SalesInvoices extends Command {
52
59
  static description = 'Manage sales invoices';
@@ -70,9 +77,11 @@ export default class SalesInvoices extends Command {
70
77
  data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
71
78
  file: Flags.string({ char: 'f', description: 'Read request body from file' }),
72
79
  set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
80
+ 'offset-id': Flags.string({ description: 'Path param: offsetId' }),
73
81
  'line-item-id': Flags.string({ description: 'Path param: lineItemId' }),
74
82
  'note-id': Flags.string({ description: 'Path param: noteId' }),
75
83
  'file-id': Flags.string({ description: 'Path param: fileId' }),
84
+ 'payment-id': Flags.string({ description: 'Path param: paymentId' }),
76
85
  page: Flags.string({ description: 'Page number' }),
77
86
  limit: Flags.string({ description: 'Items per page' }),
78
87
  include: Flags.string({ description: 'Relations to include' }),
@@ -82,6 +91,7 @@ export default class SalesInvoices extends Command {
82
91
  query: Flags.string({ description: 'Additional query params as JSON' }),
83
92
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
84
93
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
94
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
85
95
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
86
96
  };
87
97
  async run() {
@@ -374,9 +384,15 @@ export default class SalesInvoices extends Command {
374
384
  }
375
385
  catch { /* keep as string */ }
376
386
  }
377
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
387
+ // A paginated answer is { data, meta, links, totals }. The records are
388
+ // unwrapped for output, but what the API said about the page is kept:
389
+ // dropping it silently let a script read one page as the whole result
390
+ // (AderisERP/product#61).
391
+ let envelope;
378
392
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
379
- output = output.data;
393
+ const page = output;
394
+ envelope = page;
395
+ output = page.data;
380
396
  }
381
397
  // For table/csv on non-array responses, auto-extract the primary array
382
398
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -393,12 +409,24 @@ export default class SalesInvoices extends Command {
393
409
  output = this.sanitizeOutput(output);
394
410
  }
395
411
  const fieldList = flags.fields?.split(',').map(f => f.trim());
396
- if (flags.csv || flags.output === 'csv')
412
+ if (envelope)
413
+ this.warnShortPage(envelope, flags.limit);
414
+ if (flags.csv || flags.output === 'csv') {
397
415
  this.printCsv(output, fieldList);
398
- else if (flags.table || flags.output === 'table')
416
+ }
417
+ else if (flags.table || flags.output === 'table') {
399
418
  this.printTable(output, fieldList);
400
- else
419
+ if (envelope)
420
+ this.printPageSummary(envelope);
421
+ }
422
+ else if (envelope && flags.meta) {
423
+ // The whole envelope, for a caller that asked for it; the default stays
424
+ // a bare array so existing scripts are unaffected.
425
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
426
+ }
427
+ else {
401
428
  this.log(JSON.stringify(output, null, 2));
429
+ }
402
430
  }
403
431
  catch (error) {
404
432
  if (error instanceof ApiError) {
@@ -1027,6 +1055,34 @@ export default class SalesInvoices extends Command {
1027
1055
  return `\$${op}`;
1028
1056
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
1029
1057
  }
1058
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1059
+ printPageSummary(envelope) {
1060
+ const meta = envelope.meta;
1061
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1062
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1063
+ this.log(`
1064
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1065
+ }
1066
+ for (const total of envelope.totals ?? []) {
1067
+ const { currency, ...sums } = total;
1068
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1069
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1070
+ }
1071
+ }
1072
+ /**
1073
+ * The API caps how many records a page may hold. Asking for more and getting
1074
+ * fewer looks like "that is everything" unless it is said out loud.
1075
+ */
1076
+ warnShortPage(envelope, requestedLimit) {
1077
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1078
+ const perPage = envelope.meta?.itemsPerPage;
1079
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1080
+ return;
1081
+ const pages = envelope.meta?.totalPages;
1082
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1083
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1084
+ `);
1085
+ }
1030
1086
  sanitizeOutput(data) {
1031
1087
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
1032
1088
  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 SalesOrders 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 SalesOrders 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 SalesOrders 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 SalesOrders 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 SalesOrders 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 SalesOrders 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;
@@ -23,6 +23,7 @@ export default class SavedViews 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 SavedViews 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;
@@ -39,6 +39,7 @@ export default class SavedViews extends Command {
39
39
  query: Flags.string({ description: 'Additional query params as JSON' }),
40
40
  fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
41
41
  sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
42
+ meta: Flags.boolean({ description: 'Keep the API envelope in JSON output: data plus page, totals and sums' }),
42
43
  'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
43
44
  };
44
45
  async run() {
@@ -331,9 +332,15 @@ export default class SavedViews extends Command {
331
332
  }
332
333
  catch { /* keep as string */ }
333
334
  }
334
- // Extract data array from paginated wrapper (e.g., { data: [...], total: 3 })
335
+ // A paginated answer is { data, meta, links, totals }. The records are
336
+ // unwrapped for output, but what the API said about the page is kept:
337
+ // dropping it silently let a script read one page as the whole result
338
+ // (AderisERP/product#61).
339
+ let envelope;
335
340
  if (output && typeof output === 'object' && !Array.isArray(output) && 'data' in output && Array.isArray(output.data)) {
336
- output = output.data;
341
+ const page = output;
342
+ envelope = page;
343
+ output = page.data;
337
344
  }
338
345
  // For table/csv on non-array responses, auto-extract the primary array
339
346
  // e.g., { payments: [...], total: 3 } → extract payments array
@@ -350,12 +357,24 @@ export default class SavedViews extends Command {
350
357
  output = this.sanitizeOutput(output);
351
358
  }
352
359
  const fieldList = flags.fields?.split(',').map(f => f.trim());
353
- if (flags.csv || flags.output === 'csv')
360
+ if (envelope)
361
+ this.warnShortPage(envelope, flags.limit);
362
+ if (flags.csv || flags.output === 'csv') {
354
363
  this.printCsv(output, fieldList);
355
- else if (flags.table || flags.output === 'table')
364
+ }
365
+ else if (flags.table || flags.output === 'table') {
356
366
  this.printTable(output, fieldList);
357
- else
367
+ if (envelope)
368
+ this.printPageSummary(envelope);
369
+ }
370
+ else if (envelope && flags.meta) {
371
+ // The whole envelope, for a caller that asked for it; the default stays
372
+ // a bare array so existing scripts are unaffected.
373
+ this.log(JSON.stringify({ ...envelope, data: output }, null, 2));
374
+ }
375
+ else {
358
376
  this.log(JSON.stringify(output, null, 2));
377
+ }
359
378
  }
360
379
  catch (error) {
361
380
  if (error instanceof ApiError) {
@@ -984,6 +1003,34 @@ export default class SavedViews extends Command {
984
1003
  return `\$${op}`;
985
1004
  return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
986
1005
  }
1006
+ /** The page line under a table: which page, how many, and any sums the API returned. */
1007
+ printPageSummary(envelope) {
1008
+ const meta = envelope.meta;
1009
+ if (meta && typeof meta.currentPage === 'number' && typeof meta.totalPages === 'number') {
1010
+ const shown = Array.isArray(envelope.data) ? envelope.data.length : 0;
1011
+ this.log(`
1012
+ Page ${meta.currentPage}/${meta.totalPages} — ${shown} of ${meta.totalItems ?? '?'} records`);
1013
+ }
1014
+ for (const total of envelope.totals ?? []) {
1015
+ const { currency, ...sums } = total;
1016
+ const parts = Object.entries(sums).map(([column, value]) => `${column} ${value}`).join(', ');
1017
+ this.log(`Total ${currency ?? ''}: ${parts}`.trim());
1018
+ }
1019
+ }
1020
+ /**
1021
+ * The API caps how many records a page may hold. Asking for more and getting
1022
+ * fewer looks like "that is everything" unless it is said out loud.
1023
+ */
1024
+ warnShortPage(envelope, requestedLimit) {
1025
+ const asked = requestedLimit === undefined ? undefined : Number(requestedLimit);
1026
+ const perPage = envelope.meta?.itemsPerPage;
1027
+ if (asked === undefined || Number.isNaN(asked) || typeof perPage !== 'number' || perPage >= asked)
1028
+ return;
1029
+ const pages = envelope.meta?.totalPages;
1030
+ const more = typeof pages === 'number' && pages > 1 ? ` Use --page, or the export action for every page.` : '';
1031
+ process.stderr.write(`! Asked for ${asked} records per page; the API returned at most ${perPage}.${more}
1032
+ `);
1033
+ }
987
1034
  sanitizeOutput(data) {
988
1035
  const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
989
1036
  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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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 ScheduledProcessorMappings 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;
@@ -23,6 +23,7 @@ export default class Schedules 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 Schedules 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;