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