@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,9 +12,9 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["get", "update", "delete"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'get': { method: 'GET', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Get integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'update': { method: 'PUT', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Create or update integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'delete': { method: 'DELETE', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Delete integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'get': { method: 'GET', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Get integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'update': { method: 'PUT', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Create or update integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'delete': { method: 'DELETE', path: '/v1/integrations/{integrationName}/{configKey}', summary: 'Delete integration configuration', pathParams: ['integrationName', 'configKey'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
18
|
};
|
|
18
19
|
export default class Integrations extends Command {
|
|
19
20
|
static description = 'Manage integrations';
|
|
@@ -31,7 +32,7 @@ export default class Integrations extends Command {
|
|
|
31
32
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
32
33
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
33
34
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
34
|
-
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) }),
|
|
35
36
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
36
37
|
'config-key': Flags.string({ description: 'Path param: configKey' }),
|
|
37
38
|
page: Flags.string({ description: 'Page number' }),
|
|
@@ -163,6 +164,17 @@ export default class Integrations extends Command {
|
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
}
|
|
167
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
168
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
169
|
+
for (const param of endpoint.specParams) {
|
|
170
|
+
const value = flags[param.flag];
|
|
171
|
+
if (value === undefined || value === '') {
|
|
172
|
+
if (param.required)
|
|
173
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
queryParams[param.name] = value;
|
|
177
|
+
}
|
|
166
178
|
let data;
|
|
167
179
|
if (endpoint.isMultipart) {
|
|
168
180
|
if (!flags.file)
|
|
@@ -183,6 +195,25 @@ export default class Integrations extends Command {
|
|
|
183
195
|
}
|
|
184
196
|
data = form;
|
|
185
197
|
}
|
|
198
|
+
else if (endpoint.bodyMediaType) {
|
|
199
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
200
|
+
// verbatim, under the content type the endpoint asks for.
|
|
201
|
+
if (flags.file) {
|
|
202
|
+
const fsModule = await import('node:fs');
|
|
203
|
+
try {
|
|
204
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (flags.data) {
|
|
211
|
+
data = flags.data;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
186
217
|
else if (endpoint.hasBody) {
|
|
187
218
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
188
219
|
// input file — skip the read-as-body branch.
|
|
@@ -278,7 +309,7 @@ export default class Integrations extends Command {
|
|
|
278
309
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
279
310
|
}
|
|
280
311
|
else if (data) {
|
|
281
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
312
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
282
313
|
}
|
|
283
314
|
process.stderr.write('\n');
|
|
284
315
|
}
|
|
@@ -292,6 +323,7 @@ export default class Integrations extends Command {
|
|
|
292
323
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
293
324
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
294
325
|
data,
|
|
326
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
295
327
|
contextName: flags.context,
|
|
296
328
|
binary: true,
|
|
297
329
|
});
|
|
@@ -317,6 +349,7 @@ export default class Integrations extends Command {
|
|
|
317
349
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
318
350
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
319
351
|
data,
|
|
352
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
320
353
|
contextName: flags.context,
|
|
321
354
|
binary: true,
|
|
322
355
|
});
|
|
@@ -337,6 +370,7 @@ export default class Integrations extends Command {
|
|
|
337
370
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
338
371
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
339
372
|
data,
|
|
373
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
340
374
|
contextName: flags.context,
|
|
341
375
|
acceptAny: true,
|
|
342
376
|
});
|
|
@@ -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/item-categories', summary: 'Get all item categories with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/item-categories', summary: 'Create a new item category', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/item-categories/{id}', summary: 'Get an item category by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/item-categories/{id}', summary: 'Update an item category', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/item-categories/{id}', summary: 'Delete an item category', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/item-categories', summary: 'Get all item categories with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/item-categories', summary: 'Create a new item category', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'get': { method: 'GET', path: '/v1/item-categories/{id}', summary: 'Get an item category by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'update': { method: 'PATCH', path: '/v1/item-categories/{id}', summary: 'Update an item category', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'delete': { method: 'DELETE', path: '/v1/item-categories/{id}', summary: 'Delete an item category', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
20
|
};
|
|
20
21
|
export default class ItemCategories extends Command {
|
|
21
22
|
static description = 'Manage item categories';
|
|
@@ -37,7 +38,7 @@ export default class ItemCategories 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 ItemCategories 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 ItemCategories 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 ItemCategories 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 ItemCategories 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 ItemCategories 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 ItemCategories 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
|
});
|
package/dist/commands/items.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.
|
|
@@ -11,12 +12,12 @@ const SERVER_IMPORTS = ["import"];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "import"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/items', summary: 'Get all items with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/items', summary: 'Create a new item', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/items/{id}', summary: 'Get an item by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/items/{id}', summary: 'Update an item', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/items/{id}', summary: 'Delete an item', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
-
'import': { method: 'POST', path: '/v1/items/import', summary: 'Import items', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/items', summary: 'Get all items with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/items', summary: 'Create a new item', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'get': { method: 'GET', path: '/v1/items/{id}', summary: 'Get an item by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'update': { method: 'PATCH', path: '/v1/items/{id}', summary: 'Update an item', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['replacedByIds', 'tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'delete': { method: 'DELETE', path: '/v1/items/{id}', summary: 'Delete an item', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'import': { method: 'POST', path: '/v1/items/import', summary: 'Import items', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
20
21
|
};
|
|
21
22
|
export default class Items extends Command {
|
|
22
23
|
static description = 'Manage items';
|
|
@@ -38,7 +39,7 @@ export default class Items extends Command {
|
|
|
38
39
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
39
40
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
40
41
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
41
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
42
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
42
43
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
43
44
|
mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
|
|
44
45
|
reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
|
|
@@ -173,6 +174,17 @@ export default class Items 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 Items 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 Items 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 Items 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 Items 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 Items 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,7 +12,7 @@ const SERVER_IMPORTS = [];
|
|
|
11
12
|
const NESTED_UPSERTS = [];
|
|
12
13
|
const SUBCOMMANDS = [...["list"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/journal-lines', summary: 'List all journal lines (global view across all journals)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/journal-lines', summary: 'List all journal lines (global view across all journals)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
15
16
|
};
|
|
16
17
|
export default class JournalLines extends Command {
|
|
17
18
|
static description = 'Manage journal lines';
|
|
@@ -29,7 +30,7 @@ export default class JournalLines extends Command {
|
|
|
29
30
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
30
31
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
31
32
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
32
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
33
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
33
34
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
34
35
|
page: Flags.string({ description: 'Page number' }),
|
|
35
36
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -160,6 +161,17 @@ export default class JournalLines extends Command {
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
}
|
|
164
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
165
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
166
|
+
for (const param of endpoint.specParams) {
|
|
167
|
+
const value = flags[param.flag];
|
|
168
|
+
if (value === undefined || value === '') {
|
|
169
|
+
if (param.required)
|
|
170
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
queryParams[param.name] = value;
|
|
174
|
+
}
|
|
163
175
|
let data;
|
|
164
176
|
if (endpoint.isMultipart) {
|
|
165
177
|
if (!flags.file)
|
|
@@ -180,6 +192,25 @@ export default class JournalLines extends Command {
|
|
|
180
192
|
}
|
|
181
193
|
data = form;
|
|
182
194
|
}
|
|
195
|
+
else if (endpoint.bodyMediaType) {
|
|
196
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
197
|
+
// verbatim, under the content type the endpoint asks for.
|
|
198
|
+
if (flags.file) {
|
|
199
|
+
const fsModule = await import('node:fs');
|
|
200
|
+
try {
|
|
201
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (flags.data) {
|
|
208
|
+
data = flags.data;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
183
214
|
else if (endpoint.hasBody) {
|
|
184
215
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
185
216
|
// input file — skip the read-as-body branch.
|
|
@@ -275,7 +306,7 @@ export default class JournalLines extends Command {
|
|
|
275
306
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
276
307
|
}
|
|
277
308
|
else if (data) {
|
|
278
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
309
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
279
310
|
}
|
|
280
311
|
process.stderr.write('\n');
|
|
281
312
|
}
|
|
@@ -289,6 +320,7 @@ export default class JournalLines extends Command {
|
|
|
289
320
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
290
321
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
291
322
|
data,
|
|
323
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
292
324
|
contextName: flags.context,
|
|
293
325
|
binary: true,
|
|
294
326
|
});
|
|
@@ -314,6 +346,7 @@ export default class JournalLines extends Command {
|
|
|
314
346
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
315
347
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
316
348
|
data,
|
|
349
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
317
350
|
contextName: flags.context,
|
|
318
351
|
binary: true,
|
|
319
352
|
});
|
|
@@ -334,6 +367,7 @@ export default class JournalLines extends Command {
|
|
|
334
367
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
335
368
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
336
369
|
data,
|
|
370
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
337
371
|
contextName: flags.context,
|
|
338
372
|
acceptAny: true,
|
|
339
373
|
});
|
|
@@ -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,31 +12,31 @@ const SERVER_IMPORTS = ["import"];
|
|
|
11
12
|
const NESTED_UPSERTS = ["notes", "files"];
|
|
12
13
|
const SUBCOMMANDS = [...["list", "create", "stats", "get", "update", "delete", "post", "approve", "cancel", "reverse", "update-lines", "get-deferrals", "create-deferrals", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "import"], ...(HAS_UPSERT ? ['upsert'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_UPSERTS.map(n => `upsert-${n}`)];
|
|
13
14
|
const ENDPOINTS = {
|
|
14
|
-
'list': { method: 'GET', path: '/v1/journals', summary: 'List all journals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
-
'create': { method: 'POST', path: '/v1/journals', summary: 'Create a new journal (with embedded lines)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines', 'files', 'notes'] },
|
|
16
|
-
'stats': { method: 'GET', path: '/v1/journals/stats', summary: 'Journal counts and debit totals per date range', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
-
'get': { method: 'GET', path: '/v1/journals/{id}', summary: 'Get a journal by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
-
'update': { method: 'PATCH', path: '/v1/journals/{id}', summary: 'Update a journal (with embedded lines upsert)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'] },
|
|
19
|
-
'delete': { method: 'DELETE', path: '/v1/journals/{id}', summary: 'Delete a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
-
'post': { method: 'POST', path: '/v1/journals/{id}/post', summary: 'Post journal (finalize)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
-
'approve': { method: 'POST', path: '/v1/journals/{id}/approve', summary: 'Approve a journal (transitions DRAFT to APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
-
'cancel': { method: 'POST', path: '/v1/journals/{id}/cancel', summary: 'Cancel a journal', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
-
'reverse': { method: 'POST', path: '/v1/journals/{id}/reverse', summary: 'Reverse a posted journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
-
'update-lines': { method: 'PATCH', path: '/v1/journals/{id}/lines/{lineId}', summary: 'Update a single journal line in place', pathParams: ['id', 'lineId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'] },
|
|
25
|
-
'get-deferrals': { method: 'GET', path: '/v1/journals/{id}/deferrals', summary: 'List the deferral schedules of a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
|
-
'create-deferrals': { method: 'POST', path: '/v1/journals/{id}/deferrals', summary: 'Create deferral schedules for one line of a journal (manual trigger)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['periods'] },
|
|
27
|
-
'list-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes', summary: 'Get all notes for a journal', pathParams: ['journalId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
28
|
-
'create-notes': { method: 'POST', path: '/v1/journals/{journalId}/notes', summary: 'Create a note for a journal', pathParams: ['journalId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
29
|
-
'get-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Get a specific note for a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
30
|
-
'update-notes': { method: 'PATCH', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Update a note for a journal', pathParams: ['journalId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
31
|
-
'delete-notes': { method: 'DELETE', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Delete a note from a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
32
|
-
'list-files': { method: 'GET', path: '/v1/journals/{parentId}/files', summary: 'List file attachments for a journal', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
33
|
-
'create-files': { method: 'POST', path: '/v1/journals/{parentId}/files', summary: 'Upload a file attachment to a journal', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
34
|
-
'get-files': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Get a file attachment for a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
35
|
-
'update-files': { method: 'PATCH', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a journal', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
36
|
-
'delete-files': { method: 'DELETE', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
37
|
-
'files-download': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
38
|
-
'import': { method: 'POST', path: '/v1/journals/import', summary: 'Import journals', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'list': { method: 'GET', path: '/v1/journals', summary: 'List all journals', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
16
|
+
'create': { method: 'POST', path: '/v1/journals', summary: 'Create a new journal (with embedded lines)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines', 'files', 'notes'], bodyMediaType: '', specParams: [] },
|
|
17
|
+
'stats': { method: 'GET', path: '/v1/journals/stats', summary: 'Journal counts and debit totals per date range', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
18
|
+
'get': { method: 'GET', path: '/v1/journals/{id}', summary: 'Get a journal by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
19
|
+
'update': { method: 'PATCH', path: '/v1/journals/{id}', summary: 'Update a journal (with embedded lines upsert)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lines'], bodyMediaType: '', specParams: [] },
|
|
20
|
+
'delete': { method: 'DELETE', path: '/v1/journals/{id}', summary: 'Delete a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
21
|
+
'post': { method: 'POST', path: '/v1/journals/{id}/post', summary: 'Post journal (finalize)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
22
|
+
'approve': { method: 'POST', path: '/v1/journals/{id}/approve', summary: 'Approve a journal (transitions DRAFT to APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
23
|
+
'cancel': { method: 'POST', path: '/v1/journals/{id}/cancel', summary: 'Cancel a journal', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
24
|
+
'reverse': { method: 'POST', path: '/v1/journals/{id}/reverse', summary: 'Reverse a posted journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
25
|
+
'update-lines': { method: 'PATCH', path: '/v1/journals/{id}/lines/{lineId}', summary: 'Update a single journal line in place', pathParams: ['id', 'lineId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tagIds', 'tagCodes'], bodyMediaType: '', specParams: [] },
|
|
26
|
+
'get-deferrals': { method: 'GET', path: '/v1/journals/{id}/deferrals', summary: 'List the deferral schedules of a journal', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
27
|
+
'create-deferrals': { method: 'POST', path: '/v1/journals/{id}/deferrals', summary: 'Create deferral schedules for one line of a journal (manual trigger)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['periods'], bodyMediaType: '', specParams: [] },
|
|
28
|
+
'list-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes', summary: 'Get all notes for a journal', pathParams: ['journalId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
29
|
+
'create-notes': { method: 'POST', path: '/v1/journals/{journalId}/notes', summary: 'Create a note for a journal', pathParams: ['journalId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
30
|
+
'get-notes': { method: 'GET', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Get a specific note for a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
31
|
+
'update-notes': { method: 'PATCH', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Update a note for a journal', pathParams: ['journalId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
32
|
+
'delete-notes': { method: 'DELETE', path: '/v1/journals/{journalId}/notes/{noteId}', summary: 'Delete a note from a journal', pathParams: ['journalId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
33
|
+
'list-files': { method: 'GET', path: '/v1/journals/{parentId}/files', summary: 'List file attachments for a journal', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
34
|
+
'create-files': { method: 'POST', path: '/v1/journals/{parentId}/files', summary: 'Upload a file attachment to a journal', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
35
|
+
'get-files': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Get a file attachment for a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
36
|
+
'update-files': { method: 'PATCH', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a journal', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
37
|
+
'delete-files': { method: 'DELETE', path: '/v1/journals/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
38
|
+
'files-download': { method: 'GET', path: '/v1/journals/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a journal', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
39
|
+
'import': { method: 'POST', path: '/v1/journals/import', summary: 'Import journals', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [], bodyMediaType: '', specParams: [] },
|
|
39
40
|
};
|
|
40
41
|
export default class Journals extends Command {
|
|
41
42
|
static description = 'Manage journals';
|
|
@@ -57,7 +58,7 @@ export default class Journals extends Command {
|
|
|
57
58
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
58
59
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
59
60
|
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
60
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
61
|
+
file: Flags.string({ char: 'f', description: 'Read request body from file', parse: async (input) => resolveUserPath(input) }),
|
|
61
62
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
62
63
|
mode: Flags.string({ description: 'Import mode: upsert (default) creates, updates or skips each record; create refuses records that already exist; update refuses records that do not', options: ['upsert', 'create', 'update'] }),
|
|
63
64
|
reference: Flags.string({ description: 'Caller-defined text stored on the import run, for tracking and filtering' }),
|
|
@@ -195,6 +196,17 @@ export default class Journals extends Command {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
}
|
|
199
|
+
// Flags generated from this endpoint's own parameters. Raw-body endpoints
|
|
200
|
+
// have them: their body is not JSON, so --set cannot carry a value there.
|
|
201
|
+
for (const param of endpoint.specParams) {
|
|
202
|
+
const value = flags[param.flag];
|
|
203
|
+
if (value === undefined || value === '') {
|
|
204
|
+
if (param.required)
|
|
205
|
+
this.error(`Missing required flag: --${param.flag}`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
queryParams[param.name] = value;
|
|
209
|
+
}
|
|
198
210
|
let data;
|
|
199
211
|
if (endpoint.isMultipart) {
|
|
200
212
|
if (!flags.file)
|
|
@@ -215,6 +227,25 @@ export default class Journals extends Command {
|
|
|
215
227
|
}
|
|
216
228
|
data = form;
|
|
217
229
|
}
|
|
230
|
+
else if (endpoint.bodyMediaType) {
|
|
231
|
+
// The body is not JSON (today only application/x-ndjson): it is sent
|
|
232
|
+
// verbatim, under the content type the endpoint asks for.
|
|
233
|
+
if (flags.file) {
|
|
234
|
+
const fsModule = await import('node:fs');
|
|
235
|
+
try {
|
|
236
|
+
data = fsModule.readFileSync(flags.file, 'utf-8');
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
this.error(`Failed to read file: ${flags.file}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (flags.data) {
|
|
243
|
+
data = flags.data;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.error(`This endpoint takes its body as ${endpoint.bodyMediaType}. Pass the file with -f / --file, or the text with --data.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
218
249
|
else if (endpoint.hasBody) {
|
|
219
250
|
// For binary-response endpoints, --file is the output path, not a JSON
|
|
220
251
|
// input file — skip the read-as-body branch.
|
|
@@ -310,7 +341,7 @@ export default class Journals extends Command {
|
|
|
310
341
|
process.stderr.write(`Body: [multipart/form-data file upload]\n`);
|
|
311
342
|
}
|
|
312
343
|
else if (data) {
|
|
313
|
-
process.stderr.write(`Body: ${JSON.stringify(data, null, 2)}\n`);
|
|
344
|
+
process.stderr.write(`Body: ${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
314
345
|
}
|
|
315
346
|
process.stderr.write('\n');
|
|
316
347
|
}
|
|
@@ -324,6 +355,7 @@ export default class Journals extends Command {
|
|
|
324
355
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
325
356
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
326
357
|
data,
|
|
358
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
327
359
|
contextName: flags.context,
|
|
328
360
|
binary: true,
|
|
329
361
|
});
|
|
@@ -349,6 +381,7 @@ export default class Journals extends Command {
|
|
|
349
381
|
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
350
382
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
351
383
|
data,
|
|
384
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
352
385
|
contextName: flags.context,
|
|
353
386
|
binary: true,
|
|
354
387
|
});
|
|
@@ -369,6 +402,7 @@ export default class Journals extends Command {
|
|
|
369
402
|
response = await apiRequest(endpoint.method, apiPath, {
|
|
370
403
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
371
404
|
data,
|
|
405
|
+
contentType: endpoint.bodyMediaType || undefined,
|
|
372
406
|
contextName: flags.context,
|
|
373
407
|
acceptAny: true,
|
|
374
408
|
});
|