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