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