@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
@@ -459,76 +459,126 @@ export default class SalesInvoices extends Command {
459
459
  return '';
460
460
  };
461
461
  const results = [];
462
- for (const item of items) {
462
+ // Stream output row-by-row so progress is visible during long imports.
463
+ // Pre-compute the identifier column width from the input items so the
464
+ // table stays aligned without buffering all rows.
465
+ // When stdout is piped (IDE terminals, tee, CI), Node switches to async-buffered
466
+ // mode and rows accumulate in the pipe buffer until it fills. Force blocking
467
+ // mode so each \n-terminated write flushes immediately.
468
+ const stdoutHandle = process.stdout._handle;
469
+ if (stdoutHandle?.setBlocking)
470
+ stdoutHandle.setBlocking(true);
471
+ const outputFormat = flags.output || 'table';
472
+ const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
473
+ const useJson = outputFormat === 'json';
474
+ const statusWidth = 8;
475
+ const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
476
+ if (useCsv) {
477
+ this.log('status,identifier,id,error');
478
+ }
479
+ else if (!useJson) {
480
+ this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
481
+ this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
482
+ }
483
+ const emitRow = (r) => {
484
+ if (useJson)
485
+ return;
486
+ if (useCsv) {
487
+ const err = (r.error || '').replace(/"/g, '""');
488
+ this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
489
+ }
490
+ else {
491
+ this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
492
+ }
493
+ };
494
+ // Server-managed fields the API rejects in create/update bodies. Stripped
495
+ // before every request so re-importing a previously-exported payload Just Works.
496
+ const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
497
+ const stripServerFields = (item) => {
498
+ const out = {};
499
+ for (const [k, v] of Object.entries(item)) {
500
+ if (!STRIP_FIELDS.has(k))
501
+ out[k] = v;
502
+ }
503
+ return out;
504
+ };
505
+ const processItem = async (item) => {
463
506
  const identifier = getItemIdentifier(item);
507
+ const body = stripServerFields(item);
508
+ // If the payload carries an id (e.g. re-importing an export), PATCH directly.
509
+ // Fall through to POST only on 404 (so a missing record still gets created).
510
+ const itemId = typeof item.id === 'string' ? item.id : undefined;
511
+ if (itemId) {
512
+ try {
513
+ const updatePath = updateEndpoint.path.replace('{id}', itemId);
514
+ await apiRequest('PATCH', updatePath, {
515
+ data: body,
516
+ contextName: flags.context,
517
+ });
518
+ return { status: 'updated', id: itemId, identifier };
519
+ }
520
+ catch (error) {
521
+ if (!(error instanceof ApiError && error.status === 404)) {
522
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
523
+ return { status: 'error', id: itemId, identifier, error: msg };
524
+ }
525
+ // 404 → record gone, try create below
526
+ }
527
+ }
464
528
  try {
465
- // Try to create
466
529
  const response = await apiRequest('POST', createEndpoint.path, {
467
- data: item,
530
+ data: body,
468
531
  contextName: flags.context,
469
532
  });
470
533
  const id = response.data?.id;
471
- results.push({ status: 'created', id: id ? String(id) : undefined, identifier });
534
+ return { status: 'created', id: id ? String(id) : undefined, identifier };
472
535
  }
473
536
  catch (error) {
474
537
  if (error instanceof ApiError && error.status === 409) {
475
- // Conflict - try to update existing
476
538
  const errorData = error.data;
477
539
  const existingId = errorData?.existingId;
478
540
  if (existingId) {
479
541
  try {
480
542
  const updatePath = updateEndpoint.path.replace('{id}', existingId);
481
543
  await apiRequest('PATCH', updatePath, {
482
- data: item,
544
+ data: body,
483
545
  contextName: flags.context,
484
546
  });
485
- results.push({ status: 'updated', id: existingId, identifier });
547
+ return { status: 'updated', id: existingId, identifier };
486
548
  }
487
549
  catch (updateError) {
488
550
  const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
489
- results.push({ status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` });
551
+ return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
490
552
  }
491
553
  }
492
- else {
493
- results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
494
- }
495
- }
496
- else {
497
- const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
498
- results.push({ status: 'error', identifier, error: msg });
554
+ return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
499
555
  }
556
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
557
+ return { status: 'error', identifier, error: msg };
500
558
  }
501
- }
559
+ };
560
+ // Worker pool: keep N requests in flight at all times. Rows are emitted as
561
+ // each request resolves (so output order may differ from input order). The
562
+ // summary at the end remains accurate regardless.
563
+ const concurrency = 4;
564
+ const queue = [...items];
565
+ await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
566
+ for (;;) {
567
+ const item = queue.shift();
568
+ if (!item)
569
+ return;
570
+ const r = await processItem(item);
571
+ results.push(r);
572
+ emitRow(r);
573
+ }
574
+ }));
502
575
  // Summary counts
503
576
  const created = results.filter(r => r.status === 'created').length;
504
577
  const updated = results.filter(r => r.status === 'updated').length;
505
578
  const errors = results.filter(r => r.status === 'error');
506
- // Output results based on format (default: table)
507
- const outputFormat = flags.output || 'table';
508
- if (outputFormat === 'json') {
579
+ if (useJson) {
509
580
  this.log(JSON.stringify(results, null, 2));
510
581
  }
511
- else if (outputFormat === 'csv' || flags.csv) {
512
- // CSV output
513
- this.log('status,identifier,id,error');
514
- for (const r of results) {
515
- const err = (r.error || '').replace(/"/g, '""');
516
- this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
517
- }
518
- }
519
- else {
520
- // Table output (default)
521
- if (results.length > 0) {
522
- const statusWidth = 8;
523
- const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
524
- this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
525
- this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
526
- for (const r of results) {
527
- const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
528
- this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
529
- }
530
- }
531
- }
532
582
  // Always show summary at the end
533
583
  this.log('');
534
584
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
@@ -808,80 +858,122 @@ export default class SalesInvoices extends Command {
808
858
  return '';
809
859
  };
810
860
  const results = [];
811
- for (const item of items) {
861
+ const stdoutHandle = process.stdout._handle;
862
+ if (stdoutHandle?.setBlocking)
863
+ stdoutHandle.setBlocking(true);
864
+ const outputFormat = flags.output || 'table';
865
+ const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
866
+ const useJson = outputFormat === 'json';
867
+ const statusWidth = 8;
868
+ const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
869
+ if (useCsv) {
870
+ this.log('status,identifier,id,error');
871
+ }
872
+ else if (!useJson) {
873
+ this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
874
+ this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
875
+ }
876
+ const emitRow = (r) => {
877
+ if (useJson)
878
+ return;
879
+ if (useCsv) {
880
+ const err = (r.error || '').replace(/"/g, '""');
881
+ this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
882
+ }
883
+ else {
884
+ this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
885
+ }
886
+ };
887
+ const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
888
+ const stripServerFields = (item) => {
889
+ const out = {};
890
+ for (const [k, v] of Object.entries(item)) {
891
+ if (!STRIP_FIELDS.has(k))
892
+ out[k] = v;
893
+ }
894
+ return out;
895
+ };
896
+ const buildUpdatePath = (existingId) => {
897
+ const firstUpdateParam = updateEndpoint.pathParams[0];
898
+ const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
899
+ let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
900
+ if (nestedIdParam) {
901
+ updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
902
+ }
903
+ return updatePath;
904
+ };
905
+ const processItem = async (item) => {
812
906
  const identifier = getItemIdentifier(item);
907
+ const body = stripServerFields(item);
908
+ const itemId = typeof item.id === 'string' ? item.id : undefined;
909
+ if (itemId) {
910
+ try {
911
+ await apiRequest('PATCH', buildUpdatePath(itemId), {
912
+ data: body,
913
+ contextName: flags.context,
914
+ });
915
+ return { status: 'updated', id: itemId, identifier };
916
+ }
917
+ catch (error) {
918
+ if (!(error instanceof ApiError && error.status === 404)) {
919
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
920
+ return { status: 'error', id: itemId, identifier, error: msg };
921
+ }
922
+ // 404 → fall through and try create
923
+ }
924
+ }
813
925
  try {
814
- // Try to create
815
926
  const response = await apiRequest('POST', basePath, {
816
- data: item,
927
+ data: body,
817
928
  contextName: flags.context,
818
929
  });
819
930
  const id = response.data?.id;
820
- results.push({ status: 'created', id: id ? String(id) : undefined, identifier });
931
+ return { status: 'created', id: id ? String(id) : undefined, identifier };
821
932
  }
822
933
  catch (error) {
823
934
  if (error instanceof ApiError && error.status === 409) {
824
- // Conflict - try to update existing
825
935
  const errorData = error.data;
826
936
  const existingId = errorData?.existingId || item.id;
827
937
  if (existingId) {
828
938
  try {
829
- // Find the nested ID param name (e.g., 'priceId' for prices)
830
- const firstUpdateParam = updateEndpoint.pathParams[0];
831
- const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
832
- let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
833
- if (nestedIdParam) {
834
- updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
835
- }
836
- await apiRequest('PATCH', updatePath, {
837
- data: item,
939
+ await apiRequest('PATCH', buildUpdatePath(existingId), {
940
+ data: body,
838
941
  contextName: flags.context,
839
942
  });
840
- results.push({ status: 'updated', id: existingId, identifier });
943
+ return { status: 'updated', id: existingId, identifier };
841
944
  }
842
945
  catch (updateError) {
843
946
  const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
844
- results.push({ status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` });
947
+ return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
845
948
  }
846
949
  }
847
- else {
848
- results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
849
- }
850
- }
851
- else {
852
- const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
853
- results.push({ status: 'error', identifier, error: msg });
950
+ return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
854
951
  }
952
+ const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
953
+ return { status: 'error', identifier, error: msg };
855
954
  }
856
- }
955
+ };
956
+ // Worker pool: 4 requests in flight at a time. Rows are emitted as each
957
+ // request resolves so output order may differ from input order.
958
+ const concurrency = 4;
959
+ const queue = [...items];
960
+ await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
961
+ for (;;) {
962
+ const item = queue.shift();
963
+ if (!item)
964
+ return;
965
+ const r = await processItem(item);
966
+ results.push(r);
967
+ emitRow(r);
968
+ }
969
+ }));
857
970
  // Summary counts
858
971
  const created = results.filter(r => r.status === 'created').length;
859
972
  const updated = results.filter(r => r.status === 'updated').length;
860
973
  const errors = results.filter(r => r.status === 'error');
861
- // Output results based on format (default: table)
862
- const outputFormat = flags.output || 'table';
863
- if (outputFormat === 'json') {
974
+ if (useJson) {
864
975
  this.log(JSON.stringify(results, null, 2));
865
976
  }
866
- else if (outputFormat === 'csv' || flags.csv) {
867
- this.log('status,identifier,id,error');
868
- for (const r of results) {
869
- const err = (r.error || '').replace(/"/g, '""');
870
- this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
871
- }
872
- }
873
- else {
874
- if (results.length > 0) {
875
- const statusWidth = 8;
876
- const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
877
- this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
878
- this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
879
- for (const r of results) {
880
- const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
881
- this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
882
- }
883
- }
884
- }
885
977
  this.log('');
886
978
  this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
887
979
  }
@@ -1045,12 +1137,24 @@ export default class SalesInvoices extends Command {
1045
1137
  return { cols: flatten ? cols : cols.slice(0, 8), items: items };
1046
1138
  }
1047
1139
  getValue(row, col) {
1048
- let val = row[col];
1049
- if (col.includes('.')) {
1050
- const parts = col.split('.');
1051
- val = parts.reduce((o, k) => (o && typeof o === 'object' ? o[k] : undefined), row);
1052
- }
1053
- return String(val ?? '');
1140
+ if (!col.includes('.'))
1141
+ return String(row[col] ?? '');
1142
+ const parts = col.split('.');
1143
+ // Walk the dot-path; descending through an array branches into every
1144
+ // element so leaf values get collected from each one. Multiple leaves are
1145
+ // joined with ';' (e.g. dunningReminders.id -> "uuid1;uuid2").
1146
+ const walk = (o, idx) => {
1147
+ if (idx >= parts.length)
1148
+ return [o];
1149
+ if (o == null)
1150
+ return [];
1151
+ if (Array.isArray(o))
1152
+ return o.flatMap(item => walk(item, idx));
1153
+ if (typeof o === 'object')
1154
+ return walk(o[parts[idx]], idx + 1);
1155
+ return [];
1156
+ };
1157
+ return walk(row, 0).filter(v => v !== undefined && v !== null).map(v => String(v)).join(';');
1054
1158
  }
1055
1159
  printTable(data, fields) {
1056
1160
  const { cols, items } = this.getColumns(data, fields);