@aderis-erp/cli 1.0.10 → 1.0.12
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 +178 -86
- package/dist/commands/account-transaction-allocations.js +178 -86
- package/dist/commands/account-transactions.js +181 -87
- package/dist/commands/accounts.js +178 -86
- package/dist/commands/agent-runs.d.ts +1 -0
- package/dist/commands/agent-runs.js +183 -87
- package/dist/commands/audit-log.js +178 -86
- package/dist/commands/auth/login.js +4 -3
- package/dist/commands/bank-accounts.js +178 -86
- package/dist/commands/bank-integrations.js +178 -86
- package/dist/commands/business-partners.js +178 -86
- package/dist/commands/chat.d.ts +40 -0
- package/dist/commands/chat.js +1173 -0
- package/dist/commands/config/add.js +4 -3
- package/dist/commands/credit-card-accounts.js +178 -86
- package/dist/commands/documents.js +178 -86
- package/dist/commands/dunning-configs.js +180 -88
- package/dist/commands/dunning-reminders.js +178 -86
- package/dist/commands/dunning-runs.js +178 -86
- package/dist/commands/email-transactions.d.ts +40 -0
- package/dist/commands/email-transactions.js +1173 -0
- package/dist/commands/employees.js +180 -88
- package/dist/commands/entity-configurations.d.ts +40 -0
- package/dist/commands/entity-configurations.js +1169 -0
- package/dist/commands/event-processor-mappings.js +178 -86
- package/dist/commands/files-internal.js +178 -86
- package/dist/commands/health.js +178 -86
- package/dist/commands/inbox.js +181 -89
- package/dist/commands/integrations.js +178 -86
- package/dist/commands/item-categories.js +178 -86
- package/dist/commands/items.js +178 -86
- package/dist/commands/journal-lines.js +178 -86
- package/dist/commands/journals.js +178 -86
- package/dist/commands/ledgers.js +178 -86
- package/dist/commands/office-locations.js +178 -86
- package/dist/commands/payment-methods.js +178 -86
- package/dist/commands/payment-runs.js +178 -86
- package/dist/commands/payment-terms.js +178 -86
- package/dist/commands/price-lists.js +178 -86
- package/dist/commands/processor-mappings.js +178 -86
- package/dist/commands/processors.js +181 -87
- 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 +42 -0
- package/dist/commands/purchase-orders.js +1192 -0
- package/dist/commands/reports.js +178 -86
- package/dist/commands/salary-types.js +178 -86
- package/dist/commands/sales-invoices.js +187 -93
- package/dist/commands/sales-orders.js +186 -93
- package/dist/commands/saved-views.js +178 -86
- package/dist/commands/tags.js +178 -86
- package/dist/commands/tax-codes.js +180 -88
- package/dist/commands/tax-matrix.js +178 -86
- package/dist/commands/tenants.js +180 -88
- package/dist/commands/text-templates.js +178 -86
- package/dist/commands/tools.js +178 -86
- package/dist/commands/vat-declarations.d.ts +1 -0
- package/dist/commands/vat-declarations.js +188 -89
- package/dist/commands/vendor-invoices.js +181 -87
- package/dist/commands/webhooks.js +178 -86
- package/dist/commands/workflows.js +178 -86
- 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 +1174 -328
- package/package.json +2 -1
|
@@ -6,24 +6,25 @@ const HAS_IMPORT = true;
|
|
|
6
6
|
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = ["line-items", "notes", "files"];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "pdf", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "pdf", "submit", "approve", "accept", "terminate", "cancel", "replace", "create-invoice", "send", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
11
|
'list': { method: 'GET', path: '/v1/sales-orders', summary: 'Get all sales orders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'lineItems'] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/sales-orders', summary: 'Create a new sales order', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'discounts', 'lineItems'] },
|
|
13
13
|
'get': { method: 'GET', path: '/v1/sales-orders/{id}', summary: 'Get a sales order by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/sales-orders/{id}', summary: 'Update a sales order', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'lineItems', 'removeFileIds'] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/sales-orders/{id}', summary: 'Update a sales order', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'discounts', 'lineItems', 'removeFileIds'] },
|
|
15
15
|
'delete': { method: 'DELETE', path: '/v1/sales-orders/{id}', summary: 'Delete a sales order', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
16
|
'pdf': { method: 'GET', path: '/v1/sales-orders/{id}/pdf', summary: 'Render a sales order PDF using the configured PDF processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
-
'approve': { method: 'POST', path: '/v1/sales-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
+
'submit': { method: 'POST', path: '/v1/sales-orders/{id}/submit', summary: 'Submit order for approval (DRAFT or CANCELLED → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
+
'approve': { method: 'POST', path: '/v1/sales-orders/{id}/approve', summary: 'Approve order (DRAFT, PENDING_APPROVAL or CANCELLED → APPROVED). Generates order number if missing.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
19
|
'accept': { method: 'POST', path: '/v1/sales-orders/{id}/accept', summary: 'Accept order (APPROVED → ACCEPTED). Adjusts billing start date.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
20
|
'terminate': { method: 'POST', path: '/v1/sales-orders/{id}/terminate', summary: 'Terminate order (ACCEPTED → TERMINATED). Requires billingEndDate.', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
21
21
|
'cancel': { method: 'POST', path: '/v1/sales-orders/{id}/cancel', summary: 'Cancel order (any pre-active status → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
22
22
|
'replace': { method: 'POST', path: '/v1/sales-orders/{id}/replace', summary: 'Replace order with corrected version (APPROVED → creates new DRAFT)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
23
23
|
'create-invoice': { method: 'POST', path: '/v1/sales-orders/{id}/create-invoice', summary: 'Create invoice from sales order (ACCEPTED → creates DRAFT invoice)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
24
|
-
'
|
|
24
|
+
'send': { method: 'POST', path: '/v1/sales-orders/{id}/send', summary: 'Send a sales order to the customer by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['emailCc', 'emailBcc', 'files'] },
|
|
25
|
+
'create-line-items': { method: 'POST', path: '/v1/sales-orders/{orderId}/line-items', summary: 'Add a line item to a sales order', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts'] },
|
|
25
26
|
'update-line-items-reorder': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['items'] },
|
|
26
|
-
'update-line-items': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
27
|
+
'update-line-items': { method: 'PATCH', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['discounts'] },
|
|
27
28
|
'delete-line-items': { method: 'DELETE', path: '/v1/sales-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a sales order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
28
29
|
'list-notes': { method: 'GET', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Get all notes for a sales order', pathParams: ['salesOrderId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
29
30
|
'create-notes': { method: 'POST', path: '/v1/sales-orders/{salesOrderId}/notes', summary: 'Create a note for a sales order', pathParams: ['salesOrderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
@@ -457,76 +458,126 @@ export default class SalesOrders extends Command {
|
|
|
457
458
|
return '';
|
|
458
459
|
};
|
|
459
460
|
const results = [];
|
|
460
|
-
|
|
461
|
+
// Stream output row-by-row so progress is visible during long imports.
|
|
462
|
+
// Pre-compute the identifier column width from the input items so the
|
|
463
|
+
// table stays aligned without buffering all rows.
|
|
464
|
+
// When stdout is piped (IDE terminals, tee, CI), Node switches to async-buffered
|
|
465
|
+
// mode and rows accumulate in the pipe buffer until it fills. Force blocking
|
|
466
|
+
// mode so each \n-terminated write flushes immediately.
|
|
467
|
+
const stdoutHandle = process.stdout._handle;
|
|
468
|
+
if (stdoutHandle?.setBlocking)
|
|
469
|
+
stdoutHandle.setBlocking(true);
|
|
470
|
+
const outputFormat = flags.output || 'table';
|
|
471
|
+
const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
|
|
472
|
+
const useJson = outputFormat === 'json';
|
|
473
|
+
const statusWidth = 8;
|
|
474
|
+
const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
|
|
475
|
+
if (useCsv) {
|
|
476
|
+
this.log('status,identifier,id,error');
|
|
477
|
+
}
|
|
478
|
+
else if (!useJson) {
|
|
479
|
+
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
480
|
+
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
481
|
+
}
|
|
482
|
+
const emitRow = (r) => {
|
|
483
|
+
if (useJson)
|
|
484
|
+
return;
|
|
485
|
+
if (useCsv) {
|
|
486
|
+
const err = (r.error || '').replace(/"/g, '""');
|
|
487
|
+
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
// Server-managed fields the API rejects in create/update bodies. Stripped
|
|
494
|
+
// before every request so re-importing a previously-exported payload Just Works.
|
|
495
|
+
const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
|
|
496
|
+
const stripServerFields = (item) => {
|
|
497
|
+
const out = {};
|
|
498
|
+
for (const [k, v] of Object.entries(item)) {
|
|
499
|
+
if (!STRIP_FIELDS.has(k))
|
|
500
|
+
out[k] = v;
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
};
|
|
504
|
+
const processItem = async (item) => {
|
|
461
505
|
const identifier = getItemIdentifier(item);
|
|
506
|
+
const body = stripServerFields(item);
|
|
507
|
+
// If the payload carries an id (e.g. re-importing an export), PATCH directly.
|
|
508
|
+
// Fall through to POST only on 404 (so a missing record still gets created).
|
|
509
|
+
const itemId = typeof item.id === 'string' ? item.id : undefined;
|
|
510
|
+
if (itemId) {
|
|
511
|
+
try {
|
|
512
|
+
const updatePath = updateEndpoint.path.replace('{id}', itemId);
|
|
513
|
+
await apiRequest('PATCH', updatePath, {
|
|
514
|
+
data: body,
|
|
515
|
+
contextName: flags.context,
|
|
516
|
+
});
|
|
517
|
+
return { status: 'updated', id: itemId, identifier };
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
if (!(error instanceof ApiError && error.status === 404)) {
|
|
521
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
522
|
+
return { status: 'error', id: itemId, identifier, error: msg };
|
|
523
|
+
}
|
|
524
|
+
// 404 → record gone, try create below
|
|
525
|
+
}
|
|
526
|
+
}
|
|
462
527
|
try {
|
|
463
|
-
// Try to create
|
|
464
528
|
const response = await apiRequest('POST', createEndpoint.path, {
|
|
465
|
-
data:
|
|
529
|
+
data: body,
|
|
466
530
|
contextName: flags.context,
|
|
467
531
|
});
|
|
468
532
|
const id = response.data?.id;
|
|
469
|
-
|
|
533
|
+
return { status: 'created', id: id ? String(id) : undefined, identifier };
|
|
470
534
|
}
|
|
471
535
|
catch (error) {
|
|
472
536
|
if (error instanceof ApiError && error.status === 409) {
|
|
473
|
-
// Conflict - try to update existing
|
|
474
537
|
const errorData = error.data;
|
|
475
538
|
const existingId = errorData?.existingId;
|
|
476
539
|
if (existingId) {
|
|
477
540
|
try {
|
|
478
541
|
const updatePath = updateEndpoint.path.replace('{id}', existingId);
|
|
479
542
|
await apiRequest('PATCH', updatePath, {
|
|
480
|
-
data:
|
|
543
|
+
data: body,
|
|
481
544
|
contextName: flags.context,
|
|
482
545
|
});
|
|
483
|
-
|
|
546
|
+
return { status: 'updated', id: existingId, identifier };
|
|
484
547
|
}
|
|
485
548
|
catch (updateError) {
|
|
486
549
|
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
487
|
-
|
|
550
|
+
return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
|
|
488
551
|
}
|
|
489
552
|
}
|
|
490
|
-
|
|
491
|
-
results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
496
|
-
results.push({ status: 'error', identifier, error: msg });
|
|
553
|
+
return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
|
|
497
554
|
}
|
|
555
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
556
|
+
return { status: 'error', identifier, error: msg };
|
|
498
557
|
}
|
|
499
|
-
}
|
|
558
|
+
};
|
|
559
|
+
// Worker pool: keep N requests in flight at all times. Rows are emitted as
|
|
560
|
+
// each request resolves (so output order may differ from input order). The
|
|
561
|
+
// summary at the end remains accurate regardless.
|
|
562
|
+
const concurrency = 4;
|
|
563
|
+
const queue = [...items];
|
|
564
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
565
|
+
for (;;) {
|
|
566
|
+
const item = queue.shift();
|
|
567
|
+
if (!item)
|
|
568
|
+
return;
|
|
569
|
+
const r = await processItem(item);
|
|
570
|
+
results.push(r);
|
|
571
|
+
emitRow(r);
|
|
572
|
+
}
|
|
573
|
+
}));
|
|
500
574
|
// Summary counts
|
|
501
575
|
const created = results.filter(r => r.status === 'created').length;
|
|
502
576
|
const updated = results.filter(r => r.status === 'updated').length;
|
|
503
577
|
const errors = results.filter(r => r.status === 'error');
|
|
504
|
-
|
|
505
|
-
const outputFormat = flags.output || 'table';
|
|
506
|
-
if (outputFormat === 'json') {
|
|
578
|
+
if (useJson) {
|
|
507
579
|
this.log(JSON.stringify(results, null, 2));
|
|
508
580
|
}
|
|
509
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
510
|
-
// CSV output
|
|
511
|
-
this.log('status,identifier,id,error');
|
|
512
|
-
for (const r of results) {
|
|
513
|
-
const err = (r.error || '').replace(/"/g, '""');
|
|
514
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
// Table output (default)
|
|
519
|
-
if (results.length > 0) {
|
|
520
|
-
const statusWidth = 8;
|
|
521
|
-
const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
|
|
522
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
523
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
524
|
-
for (const r of results) {
|
|
525
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
526
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
581
|
// Always show summary at the end
|
|
531
582
|
this.log('');
|
|
532
583
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
@@ -806,80 +857,122 @@ export default class SalesOrders extends Command {
|
|
|
806
857
|
return '';
|
|
807
858
|
};
|
|
808
859
|
const results = [];
|
|
809
|
-
|
|
860
|
+
const stdoutHandle = process.stdout._handle;
|
|
861
|
+
if (stdoutHandle?.setBlocking)
|
|
862
|
+
stdoutHandle.setBlocking(true);
|
|
863
|
+
const outputFormat = flags.output || 'table';
|
|
864
|
+
const useCsv = outputFormat === 'csv' || Boolean(flags.csv);
|
|
865
|
+
const useJson = outputFormat === 'json';
|
|
866
|
+
const statusWidth = 8;
|
|
867
|
+
const identifierWidth = Math.max(10, ...items.map(i => getItemIdentifier(i).length));
|
|
868
|
+
if (useCsv) {
|
|
869
|
+
this.log('status,identifier,id,error');
|
|
870
|
+
}
|
|
871
|
+
else if (!useJson) {
|
|
872
|
+
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
873
|
+
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
874
|
+
}
|
|
875
|
+
const emitRow = (r) => {
|
|
876
|
+
if (useJson)
|
|
877
|
+
return;
|
|
878
|
+
if (useCsv) {
|
|
879
|
+
const err = (r.error || '').replace(/"/g, '""');
|
|
880
|
+
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
this.log(`${r.status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
const STRIP_FIELDS = new Set(['id', '_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
|
|
887
|
+
const stripServerFields = (item) => {
|
|
888
|
+
const out = {};
|
|
889
|
+
for (const [k, v] of Object.entries(item)) {
|
|
890
|
+
if (!STRIP_FIELDS.has(k))
|
|
891
|
+
out[k] = v;
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
};
|
|
895
|
+
const buildUpdatePath = (existingId) => {
|
|
896
|
+
const firstUpdateParam = updateEndpoint.pathParams[0];
|
|
897
|
+
const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
|
|
898
|
+
let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
|
|
899
|
+
if (nestedIdParam) {
|
|
900
|
+
updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
|
|
901
|
+
}
|
|
902
|
+
return updatePath;
|
|
903
|
+
};
|
|
904
|
+
const processItem = async (item) => {
|
|
810
905
|
const identifier = getItemIdentifier(item);
|
|
906
|
+
const body = stripServerFields(item);
|
|
907
|
+
const itemId = typeof item.id === 'string' ? item.id : undefined;
|
|
908
|
+
if (itemId) {
|
|
909
|
+
try {
|
|
910
|
+
await apiRequest('PATCH', buildUpdatePath(itemId), {
|
|
911
|
+
data: body,
|
|
912
|
+
contextName: flags.context,
|
|
913
|
+
});
|
|
914
|
+
return { status: 'updated', id: itemId, identifier };
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
if (!(error instanceof ApiError && error.status === 404)) {
|
|
918
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
919
|
+
return { status: 'error', id: itemId, identifier, error: msg };
|
|
920
|
+
}
|
|
921
|
+
// 404 → fall through and try create
|
|
922
|
+
}
|
|
923
|
+
}
|
|
811
924
|
try {
|
|
812
|
-
// Try to create
|
|
813
925
|
const response = await apiRequest('POST', basePath, {
|
|
814
|
-
data:
|
|
926
|
+
data: body,
|
|
815
927
|
contextName: flags.context,
|
|
816
928
|
});
|
|
817
929
|
const id = response.data?.id;
|
|
818
|
-
|
|
930
|
+
return { status: 'created', id: id ? String(id) : undefined, identifier };
|
|
819
931
|
}
|
|
820
932
|
catch (error) {
|
|
821
933
|
if (error instanceof ApiError && error.status === 409) {
|
|
822
|
-
// Conflict - try to update existing
|
|
823
934
|
const errorData = error.data;
|
|
824
935
|
const existingId = errorData?.existingId || item.id;
|
|
825
936
|
if (existingId) {
|
|
826
937
|
try {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const nestedIdParam = updateEndpoint.pathParams.find((p, i) => i > 0);
|
|
830
|
-
let updatePath = updateEndpoint.path.replace(`{${firstUpdateParam}}`, parentId);
|
|
831
|
-
if (nestedIdParam) {
|
|
832
|
-
updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
|
|
833
|
-
}
|
|
834
|
-
await apiRequest('PATCH', updatePath, {
|
|
835
|
-
data: item,
|
|
938
|
+
await apiRequest('PATCH', buildUpdatePath(existingId), {
|
|
939
|
+
data: body,
|
|
836
940
|
contextName: flags.context,
|
|
837
941
|
});
|
|
838
|
-
|
|
942
|
+
return { status: 'updated', id: existingId, identifier };
|
|
839
943
|
}
|
|
840
944
|
catch (updateError) {
|
|
841
945
|
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
842
|
-
|
|
946
|
+
return { status: 'error', id: existingId, identifier, error: `Update failed: ${msg}` };
|
|
843
947
|
}
|
|
844
948
|
}
|
|
845
|
-
|
|
846
|
-
results.push({ status: 'error', identifier, error: 'Conflict but no existingId returned' });
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
else {
|
|
850
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
851
|
-
results.push({ status: 'error', identifier, error: msg });
|
|
949
|
+
return { status: 'error', identifier, error: 'Conflict but no existingId returned' };
|
|
852
950
|
}
|
|
951
|
+
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
952
|
+
return { status: 'error', identifier, error: msg };
|
|
853
953
|
}
|
|
854
|
-
}
|
|
954
|
+
};
|
|
955
|
+
// Worker pool: 4 requests in flight at a time. Rows are emitted as each
|
|
956
|
+
// request resolves so output order may differ from input order.
|
|
957
|
+
const concurrency = 4;
|
|
958
|
+
const queue = [...items];
|
|
959
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
960
|
+
for (;;) {
|
|
961
|
+
const item = queue.shift();
|
|
962
|
+
if (!item)
|
|
963
|
+
return;
|
|
964
|
+
const r = await processItem(item);
|
|
965
|
+
results.push(r);
|
|
966
|
+
emitRow(r);
|
|
967
|
+
}
|
|
968
|
+
}));
|
|
855
969
|
// Summary counts
|
|
856
970
|
const created = results.filter(r => r.status === 'created').length;
|
|
857
971
|
const updated = results.filter(r => r.status === 'updated').length;
|
|
858
972
|
const errors = results.filter(r => r.status === 'error');
|
|
859
|
-
|
|
860
|
-
const outputFormat = flags.output || 'table';
|
|
861
|
-
if (outputFormat === 'json') {
|
|
973
|
+
if (useJson) {
|
|
862
974
|
this.log(JSON.stringify(results, null, 2));
|
|
863
975
|
}
|
|
864
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
865
|
-
this.log('status,identifier,id,error');
|
|
866
|
-
for (const r of results) {
|
|
867
|
-
const err = (r.error || '').replace(/"/g, '""');
|
|
868
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
else {
|
|
872
|
-
if (results.length > 0) {
|
|
873
|
-
const statusWidth = 8;
|
|
874
|
-
const identifierWidth = Math.max(10, ...results.map(r => (r.identifier || '').length));
|
|
875
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
876
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
877
|
-
for (const r of results) {
|
|
878
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
879
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
976
|
this.log('');
|
|
884
977
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
885
978
|
}
|