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