@aderis-erp/cli 1.1.4 → 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/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.js +47 -13
- 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.js +57 -23
- package/dist/commands/currencies.js +37 -3
- package/dist/commands/custody-accounts.js +41 -7
- package/dist/commands/dashboards.js +52 -18
- package/dist/commands/delivery-notes.js +69 -35
- package/dist/commands/document-checksums.d.ts +1 -0
- package/dist/commands/document-checksums.js +38 -3
- 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.js +41 -7
- package/dist/commands/financial-instruments.js +41 -7
- 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/kpis.js +44 -10
- package/dist/commands/ledgers.js +83 -49
- package/dist/commands/my-account.js +54 -20
- package/dist/commands/notifications.js +37 -3
- 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.js +38 -4
- 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 +49 -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 +81 -47
- package/dist/commands/webhooks.js +43 -9
- package/dist/commands/work-summary.js +37 -3
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/production-guard.js +2 -0
- package/oclif.manifest.json +199 -2
- 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,10 +12,10 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
|
|
16
|
-
'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
|
|
17
|
-
'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
19
|
};
|
|
19
20
|
export default class ProcessorMappings extends Command {
|
|
20
21
|
static description = 'Manage processor mappings';
|
|
@@ -35,7 +36,7 @@ export default class ProcessorMappings extends Command {
|
|
|
35
36
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
36
37
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
37
38
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
38
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
39
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
39
40
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
40
41
|
page: Flags.string({ description: 'Page number' }),
|
|
41
42
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -166,6 +167,17 @@ export default class ProcessorMappings extends Command {
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
|
170
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
171
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
172
|
+
for (const param of endpoint.specParams) {
|
|
173
|
+
const value = flags[param.flag];
|
|
174
|
+
if (value === undefined || value === '') {
|
|
175
|
+
if (param.required)
|
|
176
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
queryParams[param.name] = value;
|
|
180
|
+
}
|
|
169
181
|
let data;
|
|
170
182
|
if (endpoint.isMultipart) {
|
|
171
183
|
if (!flags.file)
|
|
@@ -186,6 +198,25 @@ export default class ProcessorMappings extends Command {
|
|
|
186
198
|
}
|
|
187
199
|
data = form;
|
|
188
200
|
}
|
|
201
|
+
else if (endpoint.bodyMediaType) {
|
|
202
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
203
|
+
// verbatim, under the content type the endpoint asks for.
|
|
204
|
+
if (flags.file) {
|
|
205
|
+
const fsModule = await import('node:fs');
|
|
206
|
+
try {
|
|
207
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (flags.data) {
|
|
214
|
+
data = flags.data;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
189
220
|
else if (endpoint.hasBody) {
|
|
190
221
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
191
222
|
// input file — skip the read-as-body branch.
|
|
@@ -281,7 +312,7 @@ export default class ProcessorMappings extends Command {
|
|
|
281
312
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
282
313
|
}
|
|
283
314
|
else if (data) {
|
|
284
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
315
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
285
316
|
}
|
|
286
317
|
process.stderr.write('\n');
|
|
287
318
|
}
|
|
@@ -295,6 +326,7 @@ export default class ProcessorMappings extends Command {
|
|
|
295
326
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
296
327
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
297
328
|
data,
|
|
329
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
298
330
|
contextName: flags.context,
|
|
299
331
|
binary: true,
|
|
300
332
|
});
|
|
@@ -320,6 +352,7 @@ export default class ProcessorMappings extends Command {
|
|
|
320
352
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
321
353
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
322
354
|
data,
|
|
355
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
323
356
|
contextName: flags.context,
|
|
324
357
|
binary: true,
|
|
325
358
|
});
|
|
@@ -340,6 +373,7 @@ export default class ProcessorMappings extends Command {
|
|
|
340
373
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
341
374
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
342
375
|
data,
|
|
376
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
343
377
|
contextName: flags.context,
|
|
344
378
|
acceptAny: true,
|
|
345
379
|
});
|
|
@@ -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
|
});
|