@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.
- package/README.md +1 -0
- package/dist/commands/access-groups.js +196 -92
- package/dist/commands/account-transaction-allocations.js +196 -92
- package/dist/commands/account-transactions.js +199 -93
- package/dist/commands/accounts.js +196 -92
- package/dist/commands/agent-runs.d.ts +1 -0
- package/dist/commands/agent-runs.js +201 -93
- package/dist/commands/audit-log.js +196 -92
- package/dist/commands/auth/login.js +4 -3
- package/dist/commands/bank-accounts.js +196 -92
- package/dist/commands/bank-integrations.js +196 -92
- package/dist/commands/business-partners.js +196 -92
- package/dist/commands/config/add.js +4 -3
- package/dist/commands/credit-card-accounts.js +196 -92
- package/dist/commands/documents.js +196 -92
- package/dist/commands/dunning-configs.js +198 -94
- package/dist/commands/dunning-reminders.js +196 -92
- package/dist/commands/dunning-runs.js +196 -92
- package/dist/commands/employees.js +198 -94
- package/dist/commands/entity-configurations.d.ts +40 -0
- package/dist/commands/entity-configurations.js +1169 -0
- package/dist/commands/event-processor-mappings.js +196 -92
- package/dist/commands/files-internal.js +196 -92
- package/dist/commands/health.js +196 -92
- package/dist/commands/inbox.js +199 -95
- package/dist/commands/integrations.js +196 -92
- package/dist/commands/item-categories.js +196 -92
- package/dist/commands/items.js +196 -92
- package/dist/commands/journal-lines.js +196 -92
- package/dist/commands/journals.js +196 -92
- package/dist/commands/ledgers.js +196 -92
- package/dist/commands/office-locations.js +196 -92
- package/dist/commands/payment-methods.js +196 -92
- package/dist/commands/payment-runs.js +196 -92
- package/dist/commands/payment-terms.js +196 -92
- package/dist/commands/price-lists.js +196 -92
- package/dist/commands/processor-mappings.js +196 -92
- package/dist/commands/processors.js +199 -93
- package/dist/commands/purchase-order-types.d.ts +40 -0
- package/dist/commands/purchase-order-types.js +1178 -0
- package/dist/commands/purchase-orders.d.ts +40 -0
- package/dist/commands/purchase-orders.js +1182 -0
- package/dist/commands/reports.js +196 -92
- package/dist/commands/salary-types.js +196 -92
- package/dist/commands/sales-invoices.js +196 -92
- package/dist/commands/sales-orders.js +200 -95
- package/dist/commands/saved-views.js +196 -92
- package/dist/commands/tags.js +196 -92
- package/dist/commands/tax-codes.js +196 -92
- package/dist/commands/tax-matrix.js +196 -92
- package/dist/commands/tenants.js +196 -92
- package/dist/commands/text-templates.js +196 -92
- package/dist/commands/tools.js +196 -92
- package/dist/commands/vat-declarations.d.ts +1 -0
- package/dist/commands/vat-declarations.js +206 -95
- package/dist/commands/vendor-invoices.js +196 -92
- package/dist/commands/webhooks.js +196 -92
- package/dist/commands/workflows.js +196 -92
- package/dist/lib/auth.js +2 -2
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +5 -0
- package/oclif.manifest.json +516 -9
- package/package.json +1 -1
|
@@ -6,13 +6,15 @@ const HAS_IMPORT = true;
|
|
|
6
6
|
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = true;
|
|
8
8
|
const NESTED_IMPORTS = ["allocations", "allocations-notes"];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-allocations", "create-allocations", "get-allocations", "update-allocations", "delete-allocations", "allocations-confirm", "import-camt053", "list-allocations-notes", "create-allocations-notes", "get-allocations-notes", "update-allocations-notes", "delete-allocations-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "allocations-reprocess-unbooked", "allocations-unbooked", "list-allocations", "create-allocations", "get-allocations", "update-allocations", "delete-allocations", "allocations-confirm", "import-camt053", "list-allocations-notes", "create-allocations-notes", "get-allocations-notes", "update-allocations-notes", "delete-allocations-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
11
|
'list': { method: 'GET', path: '/v1/account-transactions', summary: 'Get all account transactions with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
12
|
'create': { method: 'POST', path: '/v1/account-transactions', summary: 'Create a new account transaction', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocations'] },
|
|
13
13
|
'get': { method: 'GET', path: '/v1/account-transactions/{id}', summary: 'Get an account transaction by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
14
|
'update': { method: 'PATCH', path: '/v1/account-transactions/{id}', summary: 'Update an account transaction', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocations'] },
|
|
15
15
|
'delete': { method: 'DELETE', path: '/v1/account-transactions/{id}', summary: 'Delete an account transaction', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
+
'allocations-reprocess-unbooked': { method: 'POST', path: '/v1/account-transactions/allocations/reprocess-unbooked', summary: 'Internal — re-book confirmed allocations that have no journal', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['allocationIds'] },
|
|
17
|
+
'allocations-unbooked': { method: 'GET', path: '/v1/account-transactions/allocations/unbooked', summary: 'Internal — list confirmed allocations that have no journal', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
18
|
'list-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Get all allocations for an account transaction', pathParams: ['transactionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
19
|
'create-allocations': { method: 'POST', path: '/v1/account-transactions/{transactionId}/allocations', summary: 'Create an allocation for an account transaction', pathParams: ['transactionId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['notes'] },
|
|
18
20
|
'get-allocations': { method: 'GET', path: '/v1/account-transactions/{transactionId}/allocations/{allocationId}', summary: 'Get an allocation by ID', pathParams: ['transactionId', 'allocationId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
@@ -445,76 +447,126 @@ export default class AccountTransactions extends Command {
|
|
|
445
447
|
return '';
|
|
446
448
|
};
|
|
447
449
|
const results = [];
|
|
448
|
-
|
|
450
|
+
// Stream output row-by-row so progress is visible during long imports.
|
|
451
|
+
// Pre-compute the identifier column width from the input items so the
|
|
452
|
+
// table stays aligned without buffering all rows.
|
|
453
|
+
// When stdout is piped (IDE terminals, tee, CI), Node switches to async-buffered
|
|
454
|
+
// mode and rows accumulate in the pipe buffer until it fills. Force blocking
|
|
455
|
+
// mode so each \n-terminated write flushes immediately.
|
|
456
|
+
const stdoutHandle = process.stdout._handle;
|
|
457
|
+
if (stdoutHandle?.setBlocking)
|
|
458
|
+
stdoutHandle.setBlocking(true);
|
|
459
|
+
const outputFormat = flags.output || 'table';
|
|
460
|
+
const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
|
|
461
|
+
const useJson = outputFormat === 'json';
|
|
462
|
+
const statusWidth = 8;
|
|
463
|
+
const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
|
|
464
|
+
if (useCsv) {
|
|
465
|
+
this.log('status,identifier,id,error');
|
|
466
|
+
}
|
|
467
|
+
else if (!useJson) {
|
|
468
|
+
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
469
|
+
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
470
|
+
}
|
|
471
|
+
const emitRow = (r) => {
|
|
472
|
+
if (useJson)
|
|
473
|
+
return;
|
|
474
|
+
if (useCsv) {
|
|
475
|
+
const err = (r.error || '').replace(/"/g, '""');
|
|
476
|
+
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
// Server-managed fields the API rejects in create/update bodies. Stripped
|
|
483
|
+
// before every request so re-importing a previously-exported payload Just Works.
|
|
484
|
+
const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
|
|
485
|
+
const stripServerFields = (item) => {
|
|
486
|
+
const out = {};
|
|
487
|
+
for (const [k, v] of Object.entries(item)) {
|
|
488
|
+
if (!STRIP_FIELDS.has(k))
|
|
489
|
+
out[k] = v;
|
|
490
|
+
}
|
|
491
|
+
return out;
|
|
492
|
+
};
|
|
493
|
+
const processItem = async (item) => {
|
|
449
494
|
const identifier = getItemIdentifier(item);
|
|
495
|
+
const body = stripServerFields(item);
|
|
496
|
+
// If the payload carries an id (e.g. re-importing an export), PATCH directly.
|
|
497
|
+
// Fall through to POST only on 404 (so a missing record still gets created).
|
|
498
|
+
const itemId = typeof item.id === 'string' ? item.id : undefined;
|
|
499
|
+
if (itemId) {
|
|
500
|
+
try {
|
|
501
|
+
const updatePath = updateEndpoint.path.replace('{id}', itemId);
|
|
502
|
+
await apiRequest('PATCH', updatePath, {
|
|
503
|
+
data: body,
|
|
504
|
+
contextName: flags.context,
|
|
505
|
+
});
|
|
506
|
+
return { status: 'updated', id: itemId, identifier };
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
if (!(error instanceof ApiError && error.status === 404)) {
|
|
510
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
511
|
+
return { status: 'error', id: itemId, identifier, error: msg };
|
|
512
|
+
}
|
|
513
|
+
// 404 → record gone, try create below
|
|
514
|
+
}
|
|
515
|
+
}
|
|
450
516
|
try {
|
|
451
|
-
// Try to create
|
|
452
517
|
const response = await apiRequest('POST', createEndpoint.path, {
|
|
453
|
-
data:
|
|
518
|
+
data: body,
|
|
454
519
|
contextName: flags.context,
|
|
455
520
|
});
|
|
456
521
|
const id = response.data?.id;
|
|
457
|
-
|
|
522
|
+
return { status: 'created', id: id ? String(id) : undefined, identifier };
|
|
458
523
|
}
|
|
459
524
|
catch (error) {
|
|
460
525
|
if (error instanceof ApiError && error.status === 409) {
|
|
461
|
-
// Conflict - try to update existing
|
|
462
526
|
const errorData = error.data;
|
|
463
527
|
const existingId = errorData?.existingId;
|
|
464
528
|
if (existingId) {
|
|
465
529
|
try {
|
|
466
530
|
const updatePath = updateEndpoint.path.replace('{id}', existingId);
|
|
467
531
|
await apiRequest('PATCH', updatePath, {
|
|
468
|
-
data:
|
|
532
|
+
data: body,
|
|
469
533
|
contextName: flags.context,
|
|
470
534
|
});
|
|
471
|
-
|
|
535
|
+
return { status: 'updated', id: existingId, identifier };
|
|
472
536
|
}
|
|
473
537
|
catch (updateError) {
|
|
474
538
|
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
475
|
-
|
|
539
|
+
return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
|
|
476
540
|
}
|
|
477
541
|
}
|
|
478
|
-
|
|
479
|
-
results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
483
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
484
|
-
results.push({ status: 'error', identifier, error: msg });
|
|
542
|
+
return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
|
|
485
543
|
}
|
|
544
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
545
|
+
return { status: 'error', identifier, error: msg };
|
|
486
546
|
}
|
|
487
|
-
}
|
|
547
|
+
};
|
|
548
|
+
// Worker pool: keep N requests in flight at all times. Rows are emitted as
|
|
549
|
+
// each request resolves (so output order may differ from input order). The
|
|
550
|
+
// summary at the end remains accurate regardless.
|
|
551
|
+
const concurrency = 4;
|
|
552
|
+
const queue = [...items];
|
|
553
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
554
|
+
for (;;) {
|
|
555
|
+
const item = queue.shift();
|
|
556
|
+
if (!item)
|
|
557
|
+
return;
|
|
558
|
+
const r = await processItem(item);
|
|
559
|
+
results.push(r);
|
|
560
|
+
emitRow(r);
|
|
561
|
+
}
|
|
562
|
+
}));
|
|
488
563
|
// Summary counts
|
|
489
564
|
const created = results.filter(r => r.status === 'created').length;
|
|
490
565
|
const updated = results.filter(r => r.status === 'updated').length;
|
|
491
566
|
const errors = results.filter(r => r.status === 'error');
|
|
492
|
-
|
|
493
|
-
const outputFormat = flags.output || 'table';
|
|
494
|
-
if (outputFormat === 'json') {
|
|
567
|
+
if (useJson) {
|
|
495
568
|
this.log(JSON.stringify(results, null, 2));
|
|
496
569
|
}
|
|
497
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
498
|
-
// CSV output
|
|
499
|
-
this.log('status,identifier,id,error');
|
|
500
|
-
for (const r of results) {
|
|
501
|
-
const err = (r.error || '').replace(/"/g, '""');
|
|
502
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
// Table output (default)
|
|
507
|
-
if (results.length > 0) {
|
|
508
|
-
const statusWidth = 8;
|
|
509
|
-
const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
|
|
510
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
511
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
512
|
-
for (const r of results) {
|
|
513
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
514
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
570
|
// Always show summary at the end
|
|
519
571
|
this.log('');
|
|
520
572
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
@@ -794,80 +846,122 @@ export default class AccountTransactions extends Command {
|
|
|
794
846
|
return '';
|
|
795
847
|
};
|
|
796
848
|
const results = [];
|
|
797
|
-
|
|
849
|
+
const stdoutHandle = process.stdout._handle;
|
|
850
|
+
if (stdoutHandle?.setBlocking)
|
|
851
|
+
stdoutHandle.setBlocking(true);
|
|
852
|
+
const outputFormat = flags.output || 'table';
|
|
853
|
+
const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
|
|
854
|
+
const useJson = outputFormat === 'json';
|
|
855
|
+
const statusWidth = 8;
|
|
856
|
+
const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
|
|
857
|
+
if (useCsv) {
|
|
858
|
+
this.log('status,identifier,id,error');
|
|
859
|
+
}
|
|
860
|
+
else if (!useJson) {
|
|
861
|
+
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
862
|
+
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
863
|
+
}
|
|
864
|
+
const emitRow = (r) => {
|
|
865
|
+
if (useJson)
|
|
866
|
+
return;
|
|
867
|
+
if (useCsv) {
|
|
868
|
+
const err = (r.error || '').replace(/"/g, '""');
|
|
869
|
+
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
|
|
876
|
+
const stripServerFields = (item) => {
|
|
877
|
+
const out = {};
|
|
878
|
+
for (const [k, v] of Object.entries(item)) {
|
|
879
|
+
if (!STRIP_FIELDS.has(k))
|
|
880
|
+
out[k] = v;
|
|
881
|
+
}
|
|
882
|
+
return out;
|
|
883
|
+
};
|
|
884
|
+
const buildUpdatePath = (existingId) => {
|
|
885
|
+
const firstUpdateParam = updateEndpoint.pathParams[0];
|
|
886
|
+
const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
|
|
887
|
+
let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
|
|
888
|
+
if (nestedIdParam) {
|
|
889
|
+
updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
|
|
890
|
+
}
|
|
891
|
+
return updatePath;
|
|
892
|
+
};
|
|
893
|
+
const processItem = async (item) => {
|
|
798
894
|
const identifier = getItemIdentifier(item);
|
|
895
|
+
const body = stripServerFields(item);
|
|
896
|
+
const itemId = typeof item.id === 'string' ? item.id : undefined;
|
|
897
|
+
if (itemId) {
|
|
898
|
+
try {
|
|
899
|
+
await apiRequest('PATCH', buildUpdatePath(itemId), {
|
|
900
|
+
data: body,
|
|
901
|
+
contextName: flags.context,
|
|
902
|
+
});
|
|
903
|
+
return { status: 'updated', id: itemId, identifier };
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
if (!(error instanceof ApiError && error.status === 404)) {
|
|
907
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
908
|
+
return { status: 'error', id: itemId, identifier, error: msg };
|
|
909
|
+
}
|
|
910
|
+
// 404 → fall through and try create
|
|
911
|
+
}
|
|
912
|
+
}
|
|
799
913
|
try {
|
|
800
|
-
// Try to create
|
|
801
914
|
const response = await apiRequest('POST', basePath, {
|
|
802
|
-
data:
|
|
915
|
+
data: body,
|
|
803
916
|
contextName: flags.context,
|
|
804
917
|
});
|
|
805
918
|
const id = response.data?.id;
|
|
806
|
-
|
|
919
|
+
return { status: 'created', id: id ? String(id) : undefined, identifier };
|
|
807
920
|
}
|
|
808
921
|
catch (error) {
|
|
809
922
|
if (error instanceof ApiError && error.status === 409) {
|
|
810
|
-
// Conflict - try to update existing
|
|
811
923
|
const errorData = error.data;
|
|
812
924
|
const existingId = errorData?.existingId || item.id;
|
|
813
925
|
if (existingId) {
|
|
814
926
|
try {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
|
|
818
|
-
let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
|
|
819
|
-
if (nestedIdParam) {
|
|
820
|
-
updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
|
|
821
|
-
}
|
|
822
|
-
await apiRequest('PATCH', updatePath, {
|
|
823
|
-
data: item,
|
|
927
|
+
await apiRequest('PATCH', buildUpdatePath(existingId), {
|
|
928
|
+
data: body,
|
|
824
929
|
contextName: flags.context,
|
|
825
930
|
});
|
|
826
|
-
|
|
931
|
+
return { status: 'updated', id: existingId, identifier };
|
|
827
932
|
}
|
|
828
933
|
catch (updateError) {
|
|
829
934
|
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
830
|
-
|
|
935
|
+
return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
|
|
831
936
|
}
|
|
832
937
|
}
|
|
833
|
-
|
|
834
|
-
results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
839
|
-
results.push({ status: 'error', identifier, error: msg });
|
|
938
|
+
return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
|
|
840
939
|
}
|
|
940
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
941
|
+
return { status: 'error', identifier, error: msg };
|
|
841
942
|
}
|
|
842
|
-
}
|
|
943
|
+
};
|
|
944
|
+
// Worker pool: 4 requests in flight at a time. Rows are emitted as each
|
|
945
|
+
// request resolves so output order may differ from input order.
|
|
946
|
+
const concurrency = 4;
|
|
947
|
+
const queue = [...items];
|
|
948
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
949
|
+
for (;;) {
|
|
950
|
+
const item = queue.shift();
|
|
951
|
+
if (!item)
|
|
952
|
+
return;
|
|
953
|
+
const r = await processItem(item);
|
|
954
|
+
results.push(r);
|
|
955
|
+
emitRow(r);
|
|
956
|
+
}
|
|
957
|
+
}));
|
|
843
958
|
// Summary counts
|
|
844
959
|
const created = results.filter(r => r.status === 'created').length;
|
|
845
960
|
const updated = results.filter(r => r.status === 'updated').length;
|
|
846
961
|
const errors = results.filter(r => r.status === 'error');
|
|
847
|
-
|
|
848
|
-
const outputFormat = flags.output || 'table';
|
|
849
|
-
if (outputFormat === 'json') {
|
|
962
|
+
if (useJson) {
|
|
850
963
|
this.log(JSON.stringify(results, null, 2));
|
|
851
964
|
}
|
|
852
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
853
|
-
this.log('status,identifier,id,error');
|
|
854
|
-
for (const r of results) {
|
|
855
|
-
const err = (r.error || '').replace(/"/g, '""');
|
|
856
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
else {
|
|
860
|
-
if (results.length > 0) {
|
|
861
|
-
const statusWidth = 8;
|
|
862
|
-
const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
|
|
863
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
864
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
865
|
-
for (const r of results) {
|
|
866
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
867
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
965
|
this.log('');
|
|
872
966
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
873
967
|
}
|
|
@@ -1031,12 +1125,24 @@ export default class AccountTransactions extends Command {
|
|
|
1031
1125
|
return { cols: flatten ? cols : cols.slice(0, 8), items: items };
|
|
1032
1126
|
}
|
|
1033
1127
|
getValue(row, col) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1128
|
+
if (!col.includes('.'))
|
|
1129
|
+
return String(row[col] ?? '');
|
|
1130
|
+
const parts = col.split('.');
|
|
1131
|
+
// Walk the dot-path; descending through an array branches into every
|
|
1132
|
+
// element so leaf values get collected from each one. Multiple leaves are
|
|
1133
|
+
// joined with ';' (e.g. dunningReminders.id -> "uuid1;uuid2").
|
|
1134
|
+
const walk = (o, idx) => {
|
|
1135
|
+
if (idx >= parts.length)
|
|
1136
|
+
return [o];
|
|
1137
|
+
if (o == null)
|
|
1138
|
+
return [];
|
|
1139
|
+
if (Array.isArray(o))
|
|
1140
|
+
return o.flatMap(item => walk(item, idx));
|
|
1141
|
+
if (typeof o === 'object')
|
|
1142
|
+
return walk(o[parts[idx]], idx + 1);
|
|
1143
|
+
return [];
|
|
1144
|
+
};
|
|
1145
|
+
return walk(row, 0).filter(v => v !== undefined && v !== null).map(v => String(v)).join(';');
|
|
1040
1146
|
}
|
|
1041
1147
|
printTable(data, fields) {
|
|
1042
1148
|
const { cols, items } = this.getColumns(data, fields);
|