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