@aderis-erp/cli 1.0.9 → 1.0.11

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 (63) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/access-groups.js +196 -92
  3. package/dist/commands/account-transaction-allocations.js +196 -92
  4. package/dist/commands/account-transactions.js +199 -93
  5. package/dist/commands/accounts.js +196 -92
  6. package/dist/commands/agent-runs.d.ts +1 -0
  7. package/dist/commands/agent-runs.js +201 -93
  8. package/dist/commands/audit-log.js +196 -92
  9. package/dist/commands/auth/login.js +4 -3
  10. package/dist/commands/bank-accounts.js +196 -92
  11. package/dist/commands/bank-integrations.js +196 -92
  12. package/dist/commands/business-partners.js +196 -92
  13. package/dist/commands/config/add.js +4 -3
  14. package/dist/commands/credit-card-accounts.js +196 -92
  15. package/dist/commands/documents.js +196 -92
  16. package/dist/commands/dunning-configs.js +198 -94
  17. package/dist/commands/dunning-reminders.js +196 -92
  18. package/dist/commands/dunning-runs.js +196 -92
  19. package/dist/commands/employees.js +198 -94
  20. package/dist/commands/entity-configurations.d.ts +40 -0
  21. package/dist/commands/entity-configurations.js +1169 -0
  22. package/dist/commands/event-processor-mappings.js +196 -92
  23. package/dist/commands/files-internal.js +196 -92
  24. package/dist/commands/health.js +196 -92
  25. package/dist/commands/inbox.js +199 -95
  26. package/dist/commands/integrations.js +196 -92
  27. package/dist/commands/item-categories.js +196 -92
  28. package/dist/commands/items.js +196 -92
  29. package/dist/commands/journal-lines.js +196 -92
  30. package/dist/commands/journals.js +196 -92
  31. package/dist/commands/ledgers.js +196 -92
  32. package/dist/commands/office-locations.js +196 -92
  33. package/dist/commands/payment-methods.js +196 -92
  34. package/dist/commands/payment-runs.js +196 -92
  35. package/dist/commands/payment-terms.js +196 -92
  36. package/dist/commands/price-lists.js +196 -92
  37. package/dist/commands/processor-mappings.js +196 -92
  38. package/dist/commands/processors.js +199 -93
  39. package/dist/commands/purchase-order-types.d.ts +40 -0
  40. package/dist/commands/purchase-order-types.js +1178 -0
  41. package/dist/commands/purchase-orders.d.ts +40 -0
  42. package/dist/commands/purchase-orders.js +1182 -0
  43. package/dist/commands/reports.js +196 -92
  44. package/dist/commands/salary-types.js +196 -92
  45. package/dist/commands/sales-invoices.js +196 -92
  46. package/dist/commands/sales-orders.js +200 -95
  47. package/dist/commands/saved-views.js +196 -92
  48. package/dist/commands/tags.js +196 -92
  49. package/dist/commands/tax-codes.js +196 -92
  50. package/dist/commands/tax-matrix.js +196 -92
  51. package/dist/commands/tenants.js +196 -92
  52. package/dist/commands/text-templates.js +196 -92
  53. package/dist/commands/tools.js +196 -92
  54. package/dist/commands/vat-declarations.d.ts +1 -0
  55. package/dist/commands/vat-declarations.js +206 -95
  56. package/dist/commands/vendor-invoices.js +196 -92
  57. package/dist/commands/webhooks.js +196 -92
  58. package/dist/commands/workflows.js +196 -92
  59. package/dist/lib/auth.js +2 -2
  60. package/dist/lib/config.d.ts +1 -1
  61. package/dist/lib/config.js +5 -0
  62. package/oclif.manifest.json +516 -9
  63. package/package.json +1 -1
@@ -433,76 +433,126 @@ export default class Webhooks extends Command {
433
433
  return '';
434
434
  };
435
435
  const results = [];
436
- for (const item of items) {
436
+ // Stream output row-by-row so progress is visible during long imports.
437
+ // Pre-compute the identifier column width from the input items so the
438
+ // table stays aligned without buffering all rows.
439
+ // When stdout is piped (IDE terminals, tee, CI), Node switches to async-buffered
440
+ // mode and rows accumulate in the pipe buffer until it fills. Force blocking
441
+ // mode so each \n-terminated write flushes immediately.
442
+ const stdoutHandle = process.stdout._handle;
443
+ if (stdoutHandle?.setBlocking)
444
+ stdoutHandle.setBlocking(true);
445
+ const outputFormat = flags.output || 'table';
446
+ const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
447
+ const useJson = outputFormat === 'json';
448
+ const statusWidth = 8;
449
+ const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
450
+ if (useCsv) {
451
+ this.log('status,identifier,id,error');
452
+ }
453
+ else if (!useJson) {
454
+ this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
455
+ this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
456
+ }
457
+ const emitRow = (r) => {
458
+ if (useJson)
459
+ return;
460
+ if (useCsv) {
461
+ const err = (r.error || '').replace(/"/g, '""');
462
+ this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
463
+ }
464
+ else {
465
+ this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
466
+ }
467
+ };
468
+ // Server-managed fields the API rejects in create/update bodies. Stripped
469
+ // before every request so re-importing a previously-exported payload Just Works.
470
+ const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
471
+ const stripServerFields = (item) => {
472
+ const out = {};
473
+ for (const [k, v] of Object.entries(item)) {
474
+ if (!STRIP_FIELDS.has(k))
475
+ out[k] = v;
476
+ }
477
+ return out;
478
+ };
479
+ const processItem = async (item) => {
437
480
  const identifier = getItemIdentifier(item);
481
+ const body = stripServerFields(item);
482
+ // If the payload carries an id (e.g. re-importing an export), PATCH directly.
483
+ // Fall through to POST only on 404 (so a missing record still gets created).
484
+ const itemId = typeof item.id === 'string' ? item.id : undefined;
485
+ if (itemId) {
486
+ try {
487
+ const updatePath = updateEndpoint.path.replace('{id}', itemId);
488
+ await apiRequest('PATCH', updatePath, {
489
+ data: body,
490
+ contextName: flags.context,
491
+ });
492
+ return { status: 'updated', id: itemId, identifier };
493
+ }
494
+ catch (error) {
495
+ if (!(error instanceof ApiError && error.status === 404)) {
496
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
497
+ return { status: 'error', id: itemId, identifier, error: msg };
498
+ }
499
+ // 404 → record gone, try create below
500
+ }
501
+ }
438
502
  try {
439
- // Try to create
440
503
  const response = await apiRequest('POST', createEndpoint.path, {
441
- data: item,
504
+ data: body,
442
505
  contextName: flags.context,
443
506
  });
444
507
  const id = response.data?.id;
445
- results.push({ status: 'created', id: id ? String(id) : undefined, identifier });
508
+ return { status: 'created', id: id ? String(id) : undefined, identifier };
446
509
  }
447
510
  catch (error) {
448
511
  if (error instanceof ApiError && error.status === 409) {
449
- // Conflict - try to update existing
450
512
  const errorData = error.data;
451
513
  const existingId = errorData?.existingId;
452
514
  if (existingId) {
453
515
  try {
454
516
  const updatePath = updateEndpoint.path.replace('{id}', existingId);
455
517
  await apiRequest('PATCH', updatePath, {
456
- data: item,
518
+ data: body,
457
519
  contextName: flags.context,
458
520
  });
459
- results.push({ status: 'updated', id: existingId, identifier });
521
+ return { status: 'updated', id: existingId, identifier };
460
522
  }
461
523
  catch (updateError) {
462
524
  const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
463
- results.push({ status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` });
525
+ return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
464
526
  }
465
527
  }
466
- else {
467
- results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
468
- }
469
- }
470
- else {
471
- const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
472
- results.push({ status: 'error', identifier, error: msg });
528
+ return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
473
529
  }
530
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
531
+ return { status: 'error', identifier, error: msg };
474
532
  }
475
- }
533
+ };
534
+ // Worker pool: keep N requests in flight at all times. Rows are emitted as
535
+ // each request resolves (so output order may differ from input order). The
536
+ // summary at the end remains accurate regardless.
537
+ const concurrency = 4;
538
+ const queue = [...items];
539
+ await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
540
+ for (;;) {
541
+ const item = queue.shift();
542
+ if (!item)
543
+ return;
544
+ const r = await processItem(item);
545
+ results.push(r);
546
+ emitRow(r);
547
+ }
548
+ }));
476
549
  // Summary counts
477
550
  const created = results.filter(r => r.status === 'created').length;
478
551
  const updated = results.filter(r => r.status === 'updated').length;
479
552
  const errors = results.filter(r => r.status === 'error');
480
- // Output results based on format (default: table)
481
- const outputFormat = flags.output || 'table';
482
- if (outputFormat === 'json') {
553
+ if (useJson) {
483
554
  this.log(JSON.stringify(results, null, 2));
484
555
  }
485
- else if (outputFormat === 'csv' || flags.csv) {
486
- // CSV output
487
- this.log('status,identifier,id,error');
488
- for (const r of results) {
489
- const err = (r.error || '').replace(/"/g, '""');
490
- this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
491
- }
492
- }
493
- else {
494
- // Table output (default)
495
- if (results.length > 0) {
496
- const statusWidth = 8;
497
- const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
498
- this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
499
- this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
500
- for (const r of results) {
501
- const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
502
- this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
503
- }
504
- }
505
- }
506
556
  // Always show summary at the end
507
557
  this.log('');
508
558
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
@@ -782,80 +832,122 @@ export default class Webhooks extends Command {
782
832
  return '';
783
833
  };
784
834
  const results = [];
785
- for (const item of items) {
835
+ const stdoutHandle = process.stdout._handle;
836
+ if (stdoutHandle?.setBlocking)
837
+ stdoutHandle.setBlocking(true);
838
+ const outputFormat = flags.output || 'table';
839
+ const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
840
+ const useJson = outputFormat === 'json';
841
+ const statusWidth = 8;
842
+ const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
843
+ if (useCsv) {
844
+ this.log('status,identifier,id,error');
845
+ }
846
+ else if (!useJson) {
847
+ this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
848
+ this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
849
+ }
850
+ const emitRow = (r) => {
851
+ if (useJson)
852
+ return;
853
+ if (useCsv) {
854
+ const err = (r.error || '').replace(/"/g, '""');
855
+ this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
856
+ }
857
+ else {
858
+ this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
859
+ }
860
+ };
861
+ const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
862
+ const stripServerFields = (item) => {
863
+ const out = {};
864
+ for (const [k, v] of Object.entries(item)) {
865
+ if (!STRIP_FIELDS.has(k))
866
+ out[k] = v;
867
+ }
868
+ return out;
869
+ };
870
+ const buildUpdatePath = (existingId) => {
871
+ const firstUpdateParam = updateEndpoint.pathParams[0];
872
+ const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
873
+ let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
874
+ if (nestedIdParam) {
875
+ updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
876
+ }
877
+ return updatePath;
878
+ };
879
+ const processItem = async (item) => {
786
880
  const identifier = getItemIdentifier(item);
881
+ const body = stripServerFields(item);
882
+ const itemId = typeof item.id === 'string' ? item.id : undefined;
883
+ if (itemId) {
884
+ try {
885
+ await apiRequest('PATCH', buildUpdatePath(itemId), {
886
+ data: body,
887
+ contextName: flags.context,
888
+ });
889
+ return { status: 'updated', id: itemId, identifier };
890
+ }
891
+ catch (error) {
892
+ if (!(error instanceof ApiError && error.status === 404)) {
893
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
894
+ return { status: 'error', id: itemId, identifier, error: msg };
895
+ }
896
+ // 404 → fall through and try create
897
+ }
898
+ }
787
899
  try {
788
- // Try to create
789
900
  const response = await apiRequest('POST', basePath, {
790
- data: item,
901
+ data: body,
791
902
  contextName: flags.context,
792
903
  });
793
904
  const id = response.data?.id;
794
- results.push({ status: 'created', id: id ? String(id) : undefined, identifier });
905
+ return { status: 'created', id: id ? String(id) : undefined, identifier };
795
906
  }
796
907
  catch (error) {
797
908
  if (error instanceof ApiError && error.status === 409) {
798
- // Conflict - try to update existing
799
909
  const errorData = error.data;
800
910
  const existingId = errorData?.existingId || item.id;
801
911
  if (existingId) {
802
912
  try {
803
- // Find the nested ID param name (e.g., 'priceId' for prices)
804
- const firstUpdateParam = updateEndpoint.pathParams[0];
805
- const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
806
- let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
807
- if (nestedIdParam) {
808
- updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
809
- }
810
- await apiRequest('PATCH', updatePath, {
811
- data: item,
913
+ await apiRequest('PATCH', buildUpdatePath(existingId), {
914
+ data: body,
812
915
  contextName: flags.context,
813
916
  });
814
- results.push({ status: 'updated', id: existingId, identifier });
917
+ return { status: 'updated', id: existingId, identifier };
815
918
  }
816
919
  catch (updateError) {
817
920
  const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
818
- results.push({ status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` });
921
+ return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
819
922
  }
820
923
  }
821
- else {
822
- results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
823
- }
824
- }
825
- else {
826
- const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
827
- results.push({ status: 'error', identifier, error: msg });
924
+ return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
828
925
  }
926
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
927
+ return { status: 'error', identifier, error: msg };
829
928
  }
830
- }
929
+ };
930
+ // Worker pool: 4 requests in flight at a time. Rows are emitted as each
931
+ // request resolves so output order may differ from input order.
932
+ const concurrency = 4;
933
+ const queue = [...items];
934
+ await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
935
+ for (;;) {
936
+ const item = queue.shift();
937
+ if (!item)
938
+ return;
939
+ const r = await processItem(item);
940
+ results.push(r);
941
+ emitRow(r);
942
+ }
943
+ }));
831
944
  // Summary counts
832
945
  const created = results.filter(r => r.status === 'created').length;
833
946
  const updated = results.filter(r => r.status === 'updated').length;
834
947
  const errors = results.filter(r => r.status === 'error');
835
- // Output results based on format (default: table)
836
- const outputFormat = flags.output || 'table';
837
- if (outputFormat === 'json') {
948
+ if (useJson) {
838
949
  this.log(JSON.stringify(results, null, 2));
839
950
  }
840
- else if (outputFormat === 'csv' || flags.csv) {
841
- this.log('status,identifier,id,error');
842
- for (const r of results) {
843
- const err = (r.error || '').replace(/"/g, '""');
844
- this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
845
- }
846
- }
847
- else {
848
- if (results.length > 0) {
849
- const statusWidth = 8;
850
- const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
851
- this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
852
- this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
853
- for (const r of results) {
854
- const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
855
- this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
856
- }
857
- }
858
- }
859
951
  this.log('');
860
952
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
861
953
  }
@@ -1019,12 +1111,24 @@ export default class Webhooks extends Command {
1019
1111
  return { cols: flatten ? cols : cols.slice(0, 8), items: items };
1020
1112
  }
1021
1113
  getValue(row, col) {
1022
- let val = row[col];
1023
- if (col.includes('.')) {
1024
- const parts = col.split('.');
1025
- val = parts.reduce((o, k) => (o && typeof o === 'object' ? o[k] : undefined), row);
1026
- }
1027
- return String(val ?? '');
1114
+ if (!col.includes('.'))
1115
+ return String(row[col] ?? '');
1116
+ const parts = col.split('.');
1117
+ // Walk the dot-path; descending through an array branches into every
1118
+ // element so leaf values get collected from each one. Multiple leaves are
1119
+ // joined with ';' (e.g. dunningReminders.id -> "uuid1;uuid2").
1120
+ const walk = (o, idx) => {
1121
+ if (idx >= parts.length)
1122
+ return [o];
1123
+ if (o == null)
1124
+ return [];
1125
+ if (Array.isArray(o))
1126
+ return o.flatMap(item => walk(item, idx));
1127
+ if (typeof o === 'object')
1128
+ return walk(o[parts[idx]], idx + 1);
1129
+ return [];
1130
+ };
1131
+ return walk(row, 0).filter(v => v !== undefined && v !== null).map(v => String(v)).join(';');
1028
1132
  }
1029
1133
  printTable(data, fields) {
1030
1134
  const { cols, items } = this.getColumns(data, fields);