@aderis-erp/cli 1.1.3 → 1.1.5
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 +42 -8
- package/dist/commands/account-transaction-allocations.js +38 -4
- package/dist/commands/account-transactions.d.ts +2 -0
- package/dist/commands/account-transactions.js +68 -32
- package/dist/commands/accounts.js +46 -12
- package/dist/commands/agent-platform.js +37 -3
- package/dist/commands/agent-runs.js +49 -15
- package/dist/commands/analytics.js +39 -5
- package/dist/commands/api-clients.d.ts +58 -0
- package/dist/commands/api-clients.js +1375 -0
- package/dist/commands/api-keys.js +40 -6
- package/dist/commands/async-jobs.js +45 -11
- package/dist/commands/audit-log.js +38 -4
- package/dist/commands/bank-accounts.js +54 -20
- package/dist/commands/bank-integrations.js +56 -22
- package/dist/commands/business-partners.js +77 -43
- package/dist/commands/chat.js +43 -9
- package/dist/commands/credit-card-accounts.d.ts +4 -0
- package/dist/commands/credit-card-accounts.js +64 -25
- package/dist/commands/currencies.d.ts +56 -0
- package/dist/commands/currencies.js +1361 -0
- package/dist/commands/custody-accounts.d.ts +56 -0
- package/dist/commands/custody-accounts.js +1369 -0
- package/dist/commands/{integration-identities.d.ts → dashboards.d.ts} +3 -3
- package/dist/commands/dashboards.js +1381 -0
- package/dist/commands/delivery-notes.js +69 -35
- package/dist/commands/document-checksums.d.ts +57 -0
- package/dist/commands/{integration-identities.js → document-checksums.js} +48 -21
- package/dist/commands/dunning-configs.js +41 -7
- package/dist/commands/dunning-reminders.js +44 -10
- package/dist/commands/dunning-runs.js +37 -3
- package/dist/commands/email-transactions.js +39 -5
- package/dist/commands/employees.js +77 -43
- package/dist/commands/entity-configurations.js +39 -5
- package/dist/commands/event-mapping-rules.js +37 -3
- package/dist/commands/event-processor-mappings.js +41 -7
- package/dist/commands/files.js +48 -14
- package/dist/commands/financial-instrument-transactions.d.ts +56 -0
- package/dist/commands/financial-instrument-transactions.js +1369 -0
- package/dist/commands/financial-instrument-types.d.ts +56 -0
- package/dist/commands/financial-instrument-types.js +1369 -0
- package/dist/commands/financial-instruments.d.ts +56 -0
- package/dist/commands/financial-instruments.js +1369 -0
- package/dist/commands/health.js +41 -7
- package/dist/commands/imports.js +39 -5
- package/dist/commands/inbox.js +42 -8
- package/dist/commands/integrations.js +39 -5
- package/dist/commands/item-categories.js +41 -7
- package/dist/commands/items.js +42 -8
- package/dist/commands/journal-lines.js +37 -3
- package/dist/commands/journals.js +61 -27
- package/dist/commands/{workflows.d.ts → kpis.d.ts} +1 -1
- package/dist/commands/kpis.js +1371 -0
- package/dist/commands/ledgers.js +83 -49
- package/dist/commands/my-account.js +54 -20
- package/dist/commands/notifications.d.ts +56 -0
- package/dist/commands/notifications.js +1360 -0
- package/dist/commands/office-locations.js +41 -7
- package/dist/commands/payment-methods.js +46 -12
- package/dist/commands/payment-runs.js +54 -20
- package/dist/commands/payment-terms.js +42 -8
- package/dist/commands/payments.js +43 -9
- package/dist/commands/payroll-runs.js +71 -37
- package/dist/commands/permissions.js +37 -3
- package/dist/commands/price-lists.js +48 -14
- package/dist/commands/processor-mappings.js +40 -6
- package/dist/commands/processors.js +46 -12
- package/dist/commands/purchase-order-types.js +41 -7
- package/dist/commands/purchase-orders.js +60 -26
- package/dist/commands/reports.js +43 -8
- package/dist/commands/roles.js +40 -6
- package/dist/commands/salary-types.js +41 -7
- package/dist/commands/sales-invoices.js +85 -51
- package/dist/commands/sales-orders.js +69 -35
- package/dist/commands/saved-views.js +39 -5
- package/dist/commands/scheduled-processor-mappings.js +41 -7
- package/dist/commands/schedules.js +45 -11
- package/dist/commands/settings.d.ts +56 -0
- package/dist/commands/settings.js +1361 -0
- package/dist/commands/tags.js +42 -8
- package/dist/commands/tax-codes.js +44 -10
- package/dist/commands/tax-matrix.js +41 -7
- package/dist/commands/templates.js +50 -15
- package/dist/commands/tenants.js +44 -10
- package/dist/commands/tools.js +38 -4
- package/dist/commands/users.js +58 -24
- package/dist/commands/vat-declarations.js +61 -27
- package/dist/commands/vendor-invoices.js +82 -47
- package/dist/commands/webhooks.js +43 -9
- package/dist/commands/work-summary.d.ts +56 -0
- package/dist/commands/{workflows.js → work-summary.js} +49 -18
- package/dist/lib/auth/index.js +5 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/production-guard.d.ts +42 -0
- package/dist/lib/production-guard.js +94 -0
- package/oclif.manifest.json +2142 -333
- package/package.json +9 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
|
+
import { resolveUserPath } from '../lib/paths.js';
|
|
4
5
|
// 'upsert' loops create/update from the CLI, one request per record. It exists
|
|
5
6
|
// only where the API has no import endpoint of its own; those keep the name
|
|
6
7
|
// 'import' and are listed in SERVER_IMPORTS.
|
|
@@ -11,16 +12,16 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list-tools", "list", "create", "resolve", "resolve-event", "get", "update", "delete", "update-config", "delete-config"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
17
|
-
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
-
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
-
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
21
|
-
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
-
'update-config': { method: 'PUT', path: '/v1/processors/{id}/config', summary: 'Set or replace the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
-
'delete-config': { method: 'DELETE', path: '/v1/processors/{id}/config', summary: 'Remove the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
21
|
+
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'], bodyMediaType: '', specParams: [] },
|
|
22
|
+
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
23
|
+
'update-config': { method: 'PUT', path: '/v1/processors/{id}/config', summary: 'Set or replace the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
|
+
'delete-config': { method: 'DELETE', path: '/v1/processors/{id}/config', summary: 'Remove the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
25
|
};
|
|
25
26
|
export default class Processors extends Command {
|
|
26
27
|
static description = 'Manage processors';
|
|
@@ -42,7 +43,7 @@ export default class Processors extends Command {
|
|
|
42
43
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
43
44
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
44
45
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
45
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
46
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
46
47
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
47
48
|
page: Flags.string({ description: 'Page number' }),
|
|
48
49
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -173,6 +174,17 @@ export default class Processors extends Command {
|
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
}
|
|
177
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
178
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
179
|
+
for (const param of endpoint.specParams) {
|
|
180
|
+
const value = flags[param.flag];
|
|
181
|
+
if (value === undefined || value === '') {
|
|
182
|
+
if (param.required)
|
|
183
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
queryParams[param.name] = value;
|
|
187
|
+
}
|
|
176
188
|
let data;
|
|
177
189
|
if (endpoint.isMultipart) {
|
|
178
190
|
if (!flags.file)
|
|
@@ -193,6 +205,25 @@ export default class Processors extends Command {
|
|
|
193
205
|
}
|
|
194
206
|
data = form;
|
|
195
207
|
}
|
|
208
|
+
else if (endpoint.bodyMediaType) {
|
|
209
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
210
|
+
// verbatim, under the content type the endpoint asks for.
|
|
211
|
+
if (flags.file) {
|
|
212
|
+
const fsModule = await import('node:fs');
|
|
213
|
+
try {
|
|
214
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (flags.data) {
|
|
221
|
+
data = flags.data;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
196
227
|
else if (endpoint.hasBody) {
|
|
197
228
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
198
229
|
// input file — skip the read-as-body branch.
|
|
@@ -288,7 +319,7 @@ export default class Processors extends Command {
|
|
|
288
319
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
289
320
|
}
|
|
290
321
|
else if (data) {
|
|
291
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
322
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
292
323
|
}
|
|
293
324
|
process.stderr.write('\n');
|
|
294
325
|
}
|
|
@@ -302,6 +333,7 @@ export default class Processors extends Command {
|
|
|
302
333
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
303
334
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
304
335
|
data,
|
|
336
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
305
337
|
contextName: flags.context,
|
|
306
338
|
binary: true,
|
|
307
339
|
});
|
|
@@ -327,6 +359,7 @@ export default class Processors extends Command {
|
|
|
327
359
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
328
360
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
329
361
|
data,
|
|
362
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
330
363
|
contextName: flags.context,
|
|
331
364
|
binary: true,
|
|
332
365
|
});
|
|
@@ -347,6 +380,7 @@ export default class Processors extends Command {
|
|
|
347
380
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
348
381
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
349
382
|
data,
|
|
383
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
350
384
|
contextName: flags.context,
|
|
351
385
|
acceptAny: true,
|
|
352
386
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
|
+
import { resolveUserPath } from '../lib/paths.js';
|
|
4
5
|
// 'upsert' loops create/update from the CLI, one request per record. It exists
|
|
5
6
|
// only where the API has no import endpoint of its own; those keep the name
|
|
6
7
|
// 'import' and are listed in SERVER_IMPORTS.
|
|
@@ -11,11 +12,11 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/purchase-order-types', summary: 'Get all purchase order types with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/purchase-order-types', summary: 'Create a new purchase order type', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/purchase-order-types/{id}', summary: 'Get a purchase order type by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/purchase-order-types/{id}', summary: 'Update a purchase order type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/purchase-order-types/{id}', summary: 'Delete a purchase order type', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/purchase-order-types', summary: 'Get all purchase order types with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/purchase-order-types', summary: 'Create a new purchase order type', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'get': { method: 'GET', path: '/v1/purchase-order-types/{id}', summary: 'Get a purchase order type by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'update': { method: 'PATCH', path: '/v1/purchase-order-types/{id}', summary: 'Update a purchase order type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'delete': { method: 'DELETE', path: '/v1/purchase-order-types/{id}', summary: 'Delete a purchase order type', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
20
|
};
|
|
20
21
|
export default class PurchaseOrderTypes extends Command {
|
|
21
22
|
static description = 'Manage purchase order types';
|
|
@@ -37,7 +38,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
37
38
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
38
39
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
39
40
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
40
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
41
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
41
42
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
42
43
|
page: Flags.string({ description: 'Page number' }),
|
|
43
44
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -168,6 +169,17 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
}
|
|
172
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
173
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
174
|
+
for (const param of endpoint.specParams) {
|
|
175
|
+
const value = flags[param.flag];
|
|
176
|
+
if (value === undefined || value === '') {
|
|
177
|
+
if (param.required)
|
|
178
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
queryParams[param.name] = value;
|
|
182
|
+
}
|
|
171
183
|
let data;
|
|
172
184
|
if (endpoint.isMultipart) {
|
|
173
185
|
if (!flags.file)
|
|
@@ -188,6 +200,25 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
188
200
|
}
|
|
189
201
|
data = form;
|
|
190
202
|
}
|
|
203
|
+
else if (endpoint.bodyMediaType) {
|
|
204
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
205
|
+
// verbatim, under the content type the endpoint asks for.
|
|
206
|
+
if (flags.file) {
|
|
207
|
+
const fsModule = await import('node:fs');
|
|
208
|
+
try {
|
|
209
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (flags.data) {
|
|
216
|
+
data = flags.data;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
191
222
|
else if (endpoint.hasBody) {
|
|
192
223
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
193
224
|
// input file — skip the read-as-body branch.
|
|
@@ -283,7 +314,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
283
314
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
284
315
|
}
|
|
285
316
|
else if (data) {
|
|
286
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
317
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
287
318
|
}
|
|
288
319
|
process.stderr.write('\n');
|
|
289
320
|
}
|
|
@@ -297,6 +328,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
297
328
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
298
329
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
299
330
|
data,
|
|
331
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
300
332
|
contextName: flags.context,
|
|
301
333
|
binary: true,
|
|
302
334
|
});
|
|
@@ -322,6 +354,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
322
354
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
323
355
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
324
356
|
data,
|
|
357
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
325
358
|
contextName: flags.context,
|
|
326
359
|
binary: true,
|
|
327
360
|
});
|
|
@@ -342,6 +375,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
342
375
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
343
376
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
344
377
|
data,
|
|
378
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
345
379
|
contextName: flags.context,
|
|
346
380
|
acceptAny: true,
|
|
347
381
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
|
+
import { resolveUserPath } from '../lib/paths.js';
|
|
4
5
|
// 'upsert' loops create/update from the CLI, one request per record. It exists
|
|
5
6
|
// only where the API has no import endpoint of its own; those keep the name
|
|
6
7
|
// 'import' and are listed in SERVER_IMPORTS.
|
|
@@ -11,30 +12,30 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = ["line-items", "notes", "files"];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "copy", "decline", "cancel", "create-line-items", "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_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/purchase-orders', summary: 'Get all purchase orders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/purchase-orders', summary: 'Create a new purchase order', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'files', 'notes', 'discounts', 'lineItems'] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/purchase-orders/{id}', summary: 'Get a purchase order by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/purchase-orders/{id}', summary: 'Update a purchase order', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'files', 'notes', 'discounts', 'lineItems', 'removeFileIds'] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/purchase-orders/{id}', summary: 'Delete a purchase order', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
-
'submit': { method: 'POST', path: '/v1/purchase-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
-
'approve': { method: 'POST', path: '/v1/purchase-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number if missing.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
-
'copy': { method: 'POST', path: '/v1/purchase-orders/{id}/copy', summary: 'Create a copy of a purchase order as a new draft', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
-
'decline': { method: 'POST', path: '/v1/purchase-orders/{id}/decline', summary: 'Decline order (PENDING_APPROVAL → DECLINED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
-
'cancel': { method: 'POST', path: '/v1/purchase-orders/{id}/cancel', summary: 'Cancel order (DRAFT, PENDING_APPROVAL or APPROVED → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
-
'create-line-items': { method: 'POST', path: '/v1/purchase-orders/{orderId}/line-items', summary: 'Add a line item to a purchase order', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['discounts', 'tagIds', 'tagCodes'] },
|
|
25
|
-
'update-line-items': { method: 'PATCH', path: '/v1/purchase-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a purchase order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['discounts', 'tagIds', 'tagCodes'] },
|
|
26
|
-
'delete-line-items': { method: 'DELETE', path: '/v1/purchase-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a purchase order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
27
|
-
'list-notes': { method: 'GET', path: '/v1/purchase-orders/{purchaseOrderId}/notes', summary: 'Get all notes for a purchase order', pathParams: ['purchaseOrderId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
28
|
-
'create-notes': { method: 'POST', path: '/v1/purchase-orders/{purchaseOrderId}/notes', summary: 'Create a note for a purchase order', pathParams: ['purchaseOrderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
29
|
-
'get-notes': { method: 'GET', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Get a specific note for a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
30
|
-
'update-notes': { method: 'PATCH', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Update a note for a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
31
|
-
'delete-notes': { method: 'DELETE', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Delete a note from a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
32
|
-
'list-files': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files', summary: 'List file attachments for a purchase order', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
33
|
-
'create-files': { method: 'POST', path: '/v1/purchase-orders/{parentId}/files', summary: 'Upload a file attachment to a purchase order', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
34
|
-
'get-files': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Get a file attachment for a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
35
|
-
'update-files': { method: 'PATCH', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a purchase order', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
36
|
-
'delete-files': { method: 'DELETE', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
37
|
-
'files-download': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/purchase-orders', summary: 'Get all purchase orders with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/purchase-orders', summary: 'Create a new purchase order', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'files', 'notes', 'discounts', 'lineItems'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'get': { method: 'GET', path: '/v1/purchase-orders/{id}', summary: 'Get a purchase order by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'update': { method: 'PATCH', path: '/v1/purchase-orders/{id}', summary: 'Update a purchase order', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes', 'files', 'notes', 'discounts', 'lineItems', 'removeFileIds'], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'delete': { method: 'DELETE', path: '/v1/purchase-orders/{id}', summary: 'Delete a purchase order', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'submit': { method: 'POST', path: '/v1/purchase-orders/{id}/submit', summary: 'Submit order for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
21
|
+
'approve': { method: 'POST', path: '/v1/purchase-orders/{id}/approve', summary: 'Approve order (PENDING_APPROVAL → APPROVED). Generates order number if missing.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
22
|
+
'copy': { method: 'POST', path: '/v1/purchase-orders/{id}/copy', summary: 'Create a copy of a purchase order as a new draft', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
23
|
+
'decline': { method: 'POST', path: '/v1/purchase-orders/{id}/decline', summary: 'Decline order (PENDING_APPROVAL → DECLINED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
|
+
'cancel': { method: 'POST', path: '/v1/purchase-orders/{id}/cancel', summary: 'Cancel order (DRAFT, PENDING_APPROVAL or APPROVED → CANCELLED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
25
|
+
'create-line-items': { method: 'POST', path: '/v1/purchase-orders/{orderId}/line-items', summary: 'Add a line item to a purchase order', pathParams: ['orderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['discounts', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
|
|
26
|
+
'update-line-items': { method: 'PATCH', path: '/v1/purchase-orders/{orderId}/line-items/{lineItemId}', summary: 'Update a line item on a purchase order', pathParams: ['orderId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['discounts', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
|
|
27
|
+
'delete-line-items': { method: 'DELETE', path: '/v1/purchase-orders/{orderId}/line-items/{lineItemId}', summary: 'Remove a line item from a purchase order', pathParams: ['orderId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
28
|
+
'list-notes': { method: 'GET', path: '/v1/purchase-orders/{purchaseOrderId}/notes', summary: 'Get all notes for a purchase order', pathParams: ['purchaseOrderId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
29
|
+
'create-notes': { method: 'POST', path: '/v1/purchase-orders/{purchaseOrderId}/notes', summary: 'Create a note for a purchase order', pathParams: ['purchaseOrderId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
30
|
+
'get-notes': { method: 'GET', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Get a specific note for a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
31
|
+
'update-notes': { method: 'PATCH', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Update a note for a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
32
|
+
'delete-notes': { method: 'DELETE', path: '/v1/purchase-orders/{purchaseOrderId}/notes/{noteId}', summary: 'Delete a note from a purchase order', pathParams: ['purchaseOrderId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
33
|
+
'list-files': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files', summary: 'List file attachments for a purchase order', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
34
|
+
'create-files': { method: 'POST', path: '/v1/purchase-orders/{parentId}/files', summary: 'Upload a file attachment to a purchase order', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
35
|
+
'get-files': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Get a file attachment for a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
36
|
+
'update-files': { method: 'PATCH', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a purchase order', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
37
|
+
'delete-files': { method: 'DELETE', path: '/v1/purchase-orders/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
38
|
+
'files-download': { method: 'GET', path: '/v1/purchase-orders/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a purchase order', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
38
39
|
};
|
|
39
40
|
export default class PurchaseOrders extends Command {
|
|
40
41
|
static description = 'Manage purchase orders';
|
|
@@ -56,7 +57,7 @@ export default class PurchaseOrders extends Command {
|
|
|
56
57
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
57
58
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
58
59
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
59
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
60
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
60
61
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
61
62
|
'line-item-id': Flags.string({ description: 'Path param: lineItemId' }),
|
|
62
63
|
'note-id': Flags.string({ description: 'Path param: noteId' }),
|
|
@@ -190,6 +191,17 @@ export default class PurchaseOrders extends Command {
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
}
|
|
194
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
195
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
196
|
+
for (const param of endpoint.specParams) {
|
|
197
|
+
const value = flags[param.flag];
|
|
198
|
+
if (value === undefined || value === '') {
|
|
199
|
+
if (param.required)
|
|
200
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
queryParams[param.name] = value;
|
|
204
|
+
}
|
|
193
205
|
let data;
|
|
194
206
|
if (endpoint.isMultipart) {
|
|
195
207
|
if (!flags.file)
|
|
@@ -210,6 +222,25 @@ export default class PurchaseOrders extends Command {
|
|
|
210
222
|
}
|
|
211
223
|
data = form;
|
|
212
224
|
}
|
|
225
|
+
else if (endpoint.bodyMediaType) {
|
|
226
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
227
|
+
// verbatim, under the content type the endpoint asks for.
|
|
228
|
+
if (flags.file) {
|
|
229
|
+
const fsModule = await import('node:fs');
|
|
230
|
+
try {
|
|
231
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (flags.data) {
|
|
238
|
+
data = flags.data;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
213
244
|
else if (endpoint.hasBody) {
|
|
214
245
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
215
246
|
// input file — skip the read-as-body branch.
|
|
@@ -305,7 +336,7 @@ export default class PurchaseOrders extends Command {
|
|
|
305
336
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
306
337
|
}
|
|
307
338
|
else if (data) {
|
|
308
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
339
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
309
340
|
}
|
|
310
341
|
process.stderr.write('\n');
|
|
311
342
|
}
|
|
@@ -319,6 +350,7 @@ export default class PurchaseOrders extends Command {
|
|
|
319
350
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
320
351
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
321
352
|
data,
|
|
353
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
322
354
|
contextName: flags.context,
|
|
323
355
|
binary: true,
|
|
324
356
|
});
|
|
@@ -344,6 +376,7 @@ export default class PurchaseOrders extends Command {
|
|
|
344
376
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
345
377
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
346
378
|
data,
|
|
379
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
347
380
|
contextName: flags.context,
|
|
348
381
|
binary: true,
|
|
349
382
|
});
|
|
@@ -364,6 +397,7 @@ export default class PurchaseOrders extends Command {
|
|
|
364
397
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
365
398
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
366
399
|
data,
|
|
400
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
367
401
|
contextName: flags.context,
|
|
368
402
|
acceptAny: true,
|
|
369
403
|
});
|
package/dist/commands/reports.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
|
+
import { resolveUserPath } from '../lib/paths.js';
|
|
4
5
|
// 'upsert' loops create/update from the CLI, one request per record. It exists
|
|
5
6
|
// only where the API has no import endpoint of its own; those keep the name
|
|
6
7
|
// 'import' and are listed in SERVER_IMPORTS.
|
|
@@ -9,13 +10,14 @@ const HAS_EXPORT = false;
|
|
|
9
10
|
const HAS_CAMT053 = false;
|
|
10
11
|
const SERVER_IMPORTS = [];
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
|
-
const SUBCOMMANDS = [...["profit-and-loss", "balance-sheet", "gl-detail", "gl-detail-export-xlsx", "gl-detail-export-pdf"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
|
+
const SUBCOMMANDS = [...["profit-and-loss", "balance-sheet", "gl-detail", "gl-detail-export-xlsx", "gl-detail-export-pdf", "financial-instrument-holdings"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'profit-and-loss': { method: 'POST', path: '/v1/reports/profit-and-loss', summary: 'Get Profit & Loss report', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: true, bodyArrayFields: ['ledgerIds', 'periods'] },
|
|
15
|
-
'balance-sheet': { method: 'GET', path: '/v1/reports/balance-sheet', summary: 'Generate Balance Sheet report', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'gl-detail': { method: 'POST', path: '/v1/reports/gl-detail', summary: 'Get GL Detail report', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
17
|
-
'gl-detail-export-xlsx': { method: 'POST', path: '/v1/reports/gl-detail/export/xlsx', summary: 'Export GL Detail report as Excel (one sheet per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
18
|
-
'gl-detail-export-pdf': { method: 'POST', path: '/v1/reports/gl-detail/export/pdf', summary: 'Export GL Detail report as PDF (one page per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'] },
|
|
15
|
+
'profit-and-loss': { method: 'POST', path: '/v1/reports/profit-and-loss', summary: 'Get Profit & Loss report', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: true, bodyArrayFields: ['ledgerIds', 'periods'], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'balance-sheet': { method: 'GET', path: '/v1/reports/balance-sheet', summary: 'Generate Balance Sheet report', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'gl-detail': { method: 'POST', path: '/v1/reports/gl-detail', summary: 'Get GL Detail report', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'gl-detail-export-xlsx': { method: 'POST', path: '/v1/reports/gl-detail/export/xlsx', summary: 'Export GL Detail report as Excel (one sheet per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'gl-detail-export-pdf': { method: 'POST', path: '/v1/reports/gl-detail/export/pdf', summary: 'Export GL Detail report as PDF (one page per account)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: ['accountIds', 'accountCodes'], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'financial-instrument-holdings': { method: 'GET', path: '/v1/reports/financial-instrument-holdings', summary: 'Get financial-instrument holdings report', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
21
|
};
|
|
20
22
|
export default class Reports extends Command {
|
|
21
23
|
static description = 'Manage reports';
|
|
@@ -30,7 +32,7 @@ export default class Reports extends Command {
|
|
|
30
32
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
31
33
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
32
34
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
33
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
35
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
34
36
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
35
37
|
page: Flags.string({ description: 'Page number' }),
|
|
36
38
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -161,6 +163,17 @@ export default class Reports extends Command {
|
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
167
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
168
|
+
for (const param of endpoint.specParams) {
|
|
169
|
+
const value = flags[param.flag];
|
|
170
|
+
if (value === undefined || value === '') {
|
|
171
|
+
if (param.required)
|
|
172
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
queryParams[param.name] = value;
|
|
176
|
+
}
|
|
164
177
|
let data;
|
|
165
178
|
if (endpoint.isMultipart) {
|
|
166
179
|
if (!flags.file)
|
|
@@ -181,6 +194,25 @@ export default class Reports extends Command {
|
|
|
181
194
|
}
|
|
182
195
|
data = form;
|
|
183
196
|
}
|
|
197
|
+
else if (endpoint.bodyMediaType) {
|
|
198
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
199
|
+
// verbatim, under the content type the endpoint asks for.
|
|
200
|
+
if (flags.file) {
|
|
201
|
+
const fsModule = await import('node:fs');
|
|
202
|
+
try {
|
|
203
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (flags.data) {
|
|
210
|
+
data = flags.data;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
184
216
|
else if (endpoint.hasBody) {
|
|
185
217
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
186
218
|
// input file — skip the read-as-body branch.
|
|
@@ -276,7 +308,7 @@ export default class Reports extends Command {
|
|
|
276
308
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
277
309
|
}
|
|
278
310
|
else if (data) {
|
|
279
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
311
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
280
312
|
}
|
|
281
313
|
process.stderr.write('\n');
|
|
282
314
|
}
|
|
@@ -290,6 +322,7 @@ export default class Reports extends Command {
|
|
|
290
322
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
291
323
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
292
324
|
data,
|
|
325
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
293
326
|
contextName: flags.context,
|
|
294
327
|
binary: true,
|
|
295
328
|
});
|
|
@@ -315,6 +348,7 @@ export default class Reports extends Command {
|
|
|
315
348
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
316
349
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
317
350
|
data,
|
|
351
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
318
352
|
contextName: flags.context,
|
|
319
353
|
binary: true,
|
|
320
354
|
});
|
|
@@ -335,6 +369,7 @@ export default class Reports extends Command {
|
|
|
335
369
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
336
370
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
337
371
|
data,
|
|
372
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
338
373
|
contextName: flags.context,
|
|
339
374
|
acceptAny: true,
|
|
340
375
|
});
|