@genz-its/sevdesk-cli 0.0.0

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/index.js ADDED
@@ -0,0 +1,1794 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { ZliError, defineCommand, defineConfig, defineOptions, processConfig } from "@robingenz/zli";
4
+ import { consola } from "consola";
5
+ import { z } from "zod";
6
+ import { SevDesk, SevDeskError } from "@genz-its/sevdesk-sdk";
7
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import { homedir } from "node:os";
9
+ import { basename, join, resolve } from "node:path";
10
+ //#region src/config.ts
11
+ function configDir() {
12
+ const xdgConfigHome = process.env["XDG_CONFIG_HOME"];
13
+ const base = xdgConfigHome ? xdgConfigHome : join(homedir(), ".config");
14
+ return join(base, "sevdesk");
15
+ }
16
+ function configPath() {
17
+ return join(configDir(), "config.json");
18
+ }
19
+ async function readConfig() {
20
+ try {
21
+ return JSON.parse(await readFile(configPath(), "utf8"));
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+ async function writeConfig(config) {
27
+ await mkdir(configDir(), { recursive: true });
28
+ await writeFile(configPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
29
+ }
30
+ async function resolveToken() {
31
+ const environmentToken = process.env["SEVDESK_TOKEN"]?.trim();
32
+ if (environmentToken) return {
33
+ token: environmentToken,
34
+ source: "environment"
35
+ };
36
+ const configToken = (await readConfig()).token?.trim();
37
+ if (configToken) return {
38
+ token: configToken,
39
+ source: "config"
40
+ };
41
+ return { source: "none" };
42
+ }
43
+ //#endregion
44
+ //#region src/package.ts
45
+ const pkg = createRequire(import.meta.url)("../package.json");
46
+ //#endregion
47
+ //#region src/client.ts
48
+ function createClient(token) {
49
+ return new SevDesk({
50
+ token,
51
+ userAgent: `${pkg.name}/${pkg.version}`
52
+ });
53
+ }
54
+ /**
55
+ * Resolves the API token and returns a ready-to-use client. Exits with an
56
+ * explanation when no token is available.
57
+ */
58
+ async function requireClient() {
59
+ const { token } = await resolveToken();
60
+ if (!token) {
61
+ consola.error("You must be logged in. Run `sevdesk login` or set the SEVDESK_TOKEN environment variable.");
62
+ process.exit(1);
63
+ }
64
+ return createClient(token);
65
+ }
66
+ //#endregion
67
+ //#region src/interactive.ts
68
+ function isInteractive() {
69
+ return process.stdin.isTTY === true && process.stdout.isTTY === true && !process.env["CI"];
70
+ }
71
+ //#endregion
72
+ //#region src/prompt.ts
73
+ async function promptText(message) {
74
+ const response = await consola.prompt(message, {
75
+ type: "text",
76
+ cancel: "symbol"
77
+ });
78
+ if (typeof response === "symbol") process.exit(0);
79
+ return response;
80
+ }
81
+ async function promptConfirm(message, initial = true) {
82
+ const response = await consola.prompt(message, {
83
+ type: "confirm",
84
+ initial,
85
+ cancel: "symbol"
86
+ });
87
+ if (typeof response === "symbol") process.exit(0);
88
+ return response;
89
+ }
90
+ //#endregion
91
+ //#region src/options.ts
92
+ /**
93
+ * Returns the option value, asking for it when it is missing. Exits when the
94
+ * value is missing in a non-interactive environment.
95
+ */
96
+ async function requireStringOption(options) {
97
+ const value = options.value?.trim();
98
+ if (value) return value;
99
+ requireInteractive(options.requirement);
100
+ const answer = (await promptText(options.question)).trim();
101
+ if (!answer) {
102
+ consola.error("The value must not be empty.");
103
+ process.exit(1);
104
+ }
105
+ return answer;
106
+ }
107
+ /**
108
+ * Returns the option value, asking for it when it is missing. Exits when the
109
+ * value is missing in a non-interactive environment or is not a number.
110
+ */
111
+ async function requireNumberOption(options) {
112
+ if (options.value !== void 0) return options.value;
113
+ requireInteractive(options.requirement);
114
+ const answer = (await promptText(options.question)).trim();
115
+ const value = Number(answer);
116
+ if (!answer || !Number.isFinite(value)) {
117
+ consola.error("The value must be a number.");
118
+ process.exit(1);
119
+ }
120
+ return value;
121
+ }
122
+ /**
123
+ * Asks for confirmation unless `yes` is set. Returns `false` when the user
124
+ * declines, in which case the command must stop. Exits when the confirmation
125
+ * is missing in a non-interactive environment.
126
+ */
127
+ async function confirmOrAbort(options) {
128
+ if (options.yes) return true;
129
+ if (!isInteractive()) {
130
+ consola.error("You must pass --yes to confirm this action when running in a non-interactive environment.");
131
+ process.exit(1);
132
+ }
133
+ const confirmed = options.initial === void 0 ? await promptConfirm(options.message) : await promptConfirm(options.message, options.initial);
134
+ if (!confirmed) consola.info("Aborted.");
135
+ return confirmed;
136
+ }
137
+ function requireInteractive(requirement) {
138
+ if (!isInteractive()) {
139
+ consola.error(`You must provide ${requirement} when running in a non-interactive environment.`);
140
+ process.exit(1);
141
+ }
142
+ }
143
+ //#endregion
144
+ //#region src/output.ts
145
+ function printJson(value) {
146
+ console.log(JSON.stringify(value, null, 2));
147
+ }
148
+ function printTable(rows) {
149
+ const first = rows[0];
150
+ if (!first) return;
151
+ const columns = Object.keys(first);
152
+ const widths = columns.map((column) => Math.max(column.length, ...rows.map((row) => (row[column] ?? "").length)));
153
+ const line = (values) => values.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" ");
154
+ console.log(line(columns.map((column) => column.toUpperCase())));
155
+ for (const row of rows) console.log(line(columns.map((column) => row[column] ?? "")));
156
+ }
157
+ //#endregion
158
+ //#region src/commands/accounts/balance.ts
159
+ var balance_default = defineCommand({
160
+ description: "Show the balance of a check account at a given date.",
161
+ options: defineOptions(z.object({
162
+ id: z.coerce.number().optional().describe("The check account ID. If omitted, you will be prompted."),
163
+ date: z.string().default(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)).describe("The date to calculate the balance for (YYYY-MM-DD)."),
164
+ json: z.boolean().default(false).describe("Output in JSON format.")
165
+ })),
166
+ action: async (options) => {
167
+ const client = await requireClient();
168
+ const checkAccountId = await requireNumberOption({
169
+ value: options.id,
170
+ requirement: "a check account ID via --id",
171
+ question: "Enter the check account ID:"
172
+ });
173
+ const balance = await client.checkAccounts.getBalanceAtDate({
174
+ checkAccountId,
175
+ date: options.date
176
+ });
177
+ if (options.json) {
178
+ printJson({
179
+ checkAccountId,
180
+ date: options.date,
181
+ balance
182
+ });
183
+ return;
184
+ }
185
+ consola.info(`Balance at ${options.date}: ${balance}`);
186
+ }
187
+ });
188
+ //#endregion
189
+ //#region src/commands/accounts/create-clearing.ts
190
+ var create_clearing_default = defineCommand({
191
+ description: "Create a clearing check account.",
192
+ options: defineOptions(z.object({
193
+ name: z.string().optional().describe("The name of the check account. If omitted, you will be prompted."),
194
+ accountingNumber: z.coerce.number().optional().describe("The booking account number of the check account."),
195
+ json: z.boolean().default(false).describe("Output in JSON format.")
196
+ })),
197
+ action: async (options) => {
198
+ const client = await requireClient();
199
+ const name = await requireStringOption({
200
+ value: options.name,
201
+ requirement: "a name via --name",
202
+ question: "Enter the name of the check account:"
203
+ });
204
+ const checkAccount = await client.checkAccounts.createClearingAccount({
205
+ name,
206
+ accountingNumber: options.accountingNumber
207
+ });
208
+ if (options.json) {
209
+ printJson(checkAccount);
210
+ return;
211
+ }
212
+ consola.success(`Created clearing account ${checkAccount.id}.`);
213
+ }
214
+ });
215
+ //#endregion
216
+ //#region src/commands/accounts/create-file-import.ts
217
+ var create_file_import_default = defineCommand({
218
+ description: "Create a check account that imports its transactions from files.",
219
+ options: defineOptions(z.object({
220
+ name: z.string().optional().describe("The name of the check account. If omitted, you will be prompted."),
221
+ importType: z.enum(["CSV", "MT940"]).default("CSV").describe("The file format used to import transactions. Supported values are CSV and MT940."),
222
+ accountingNumber: z.coerce.number().optional().describe("The booking account number of the check account."),
223
+ iban: z.string().optional().describe("The IBAN of the check account."),
224
+ json: z.boolean().default(false).describe("Output in JSON format.")
225
+ })),
226
+ action: async (options) => {
227
+ const client = await requireClient();
228
+ const name = await requireStringOption({
229
+ value: options.name,
230
+ requirement: "a name via --name",
231
+ question: "Enter the name of the check account:"
232
+ });
233
+ const checkAccount = await client.checkAccounts.createFileImportAccount({
234
+ name,
235
+ importType: options.importType,
236
+ accountingNumber: options.accountingNumber,
237
+ iban: options.iban
238
+ });
239
+ if (options.json) {
240
+ printJson(checkAccount);
241
+ return;
242
+ }
243
+ consola.success(`Created file import account ${checkAccount.id}.`);
244
+ }
245
+ });
246
+ //#endregion
247
+ //#region src/commands/accounts/get.ts
248
+ var get_default$7 = defineCommand({
249
+ description: "Show a single check account.",
250
+ options: defineOptions(z.object({
251
+ id: z.coerce.number().optional().describe("The check account ID. If omitted, you will be prompted."),
252
+ json: z.boolean().default(false).describe("Output in JSON format.")
253
+ })),
254
+ action: async (options) => {
255
+ const client = await requireClient();
256
+ const checkAccountId = await requireNumberOption({
257
+ value: options.id,
258
+ requirement: "a check account ID via --id",
259
+ question: "Enter the check account ID:"
260
+ });
261
+ const checkAccount = await client.checkAccounts.get({ checkAccountId });
262
+ if (options.json) {
263
+ printJson(checkAccount);
264
+ return;
265
+ }
266
+ consola.info(`ID: ${checkAccount.id}`);
267
+ consola.info(`Name: ${checkAccount.name}`);
268
+ consola.info(`Type: ${checkAccount.type}`);
269
+ consola.info(`Import type: ${checkAccount.importType ?? "-"}`);
270
+ consola.info(`IBAN: ${checkAccount.iban ?? "-"}`);
271
+ consola.info(`Accounting number: ${checkAccount.accountingNumber ?? "-"}`);
272
+ consola.info(`Currency: ${checkAccount.currency}`);
273
+ consola.info(`Balance: ${checkAccount.balance ?? "-"}`);
274
+ consola.info(`Status: ${checkAccount.status}`);
275
+ }
276
+ });
277
+ //#endregion
278
+ //#region src/commands/accounts/list.ts
279
+ var list_default$8 = defineCommand({
280
+ description: "List your check accounts.",
281
+ options: defineOptions(z.object({
282
+ limit: z.coerce.number().optional().describe("The maximum number of check accounts to return."),
283
+ offset: z.coerce.number().optional().describe("The number of check accounts to skip."),
284
+ json: z.boolean().default(false).describe("Output in JSON format.")
285
+ })),
286
+ action: async (options) => {
287
+ const checkAccounts = await (await requireClient()).checkAccounts.list({
288
+ limit: options.limit,
289
+ offset: options.offset
290
+ });
291
+ if (options.json) {
292
+ printJson(checkAccounts);
293
+ return;
294
+ }
295
+ if (checkAccounts.length === 0) {
296
+ consola.info("No check accounts found.");
297
+ return;
298
+ }
299
+ printTable(checkAccounts.map((checkAccount) => ({
300
+ id: checkAccount.id,
301
+ name: checkAccount.name,
302
+ type: checkAccount.type,
303
+ importType: checkAccount.importType ?? "",
304
+ accountingNumber: checkAccount.accountingNumber ?? "",
305
+ currency: checkAccount.currency,
306
+ status: checkAccount.status
307
+ })));
308
+ }
309
+ });
310
+ //#endregion
311
+ //#region src/commands/contacts/create.ts
312
+ var create_default$3 = defineCommand({
313
+ description: "Create a contact.",
314
+ options: defineOptions(z.object({
315
+ name: z.string().optional().describe("Name of the organization. Not to be used for persons. If both --name and --familyname are omitted, you will be prompted for the organization name."),
316
+ surename: z.string().optional().describe("First name of the person. Not to be used for organizations."),
317
+ familyname: z.string().optional().describe("Last name of the person. Not to be used for organizations."),
318
+ category: z.coerce.number().default(3).describe("ID of the contact category: 2 supplier, 3 customer, 4 partner."),
319
+ customerNumber: z.string().optional().describe("Customer number of the contact."),
320
+ description: z.string().optional().describe("Description of the contact."),
321
+ vatNumber: z.string().optional().describe("VAT number of the contact."),
322
+ taxNumber: z.string().optional().describe("Tax number of the contact."),
323
+ json: z.boolean().default(false).describe("Output in JSON format.")
324
+ })),
325
+ action: async (options) => {
326
+ const client = await requireClient();
327
+ const name = !options.name && !options.familyname ? await requireStringOption({
328
+ value: void 0,
329
+ requirement: "a name via --name or --familyname",
330
+ question: "Enter the organization name:"
331
+ }) : options.name;
332
+ const contact = await client.contacts.create({
333
+ categoryId: options.category,
334
+ name,
335
+ surename: options.surename,
336
+ familyname: options.familyname,
337
+ customerNumber: options.customerNumber,
338
+ description: options.description,
339
+ vatNumber: options.vatNumber,
340
+ taxNumber: options.taxNumber
341
+ });
342
+ if (options.json) {
343
+ printJson(contact);
344
+ return;
345
+ }
346
+ consola.success(`Created contact ${contact.id}.`);
347
+ }
348
+ });
349
+ //#endregion
350
+ //#region src/commands/contacts/delete.ts
351
+ var delete_default$1 = defineCommand({
352
+ description: "Delete a contact.",
353
+ options: defineOptions(z.object({
354
+ id: z.coerce.number().optional().describe("The contact ID. If omitted, you will be prompted."),
355
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
356
+ json: z.boolean().default(false).describe("Output in JSON format.")
357
+ })),
358
+ action: async (options) => {
359
+ const client = await requireClient();
360
+ const contactId = await requireNumberOption({
361
+ value: options.id,
362
+ requirement: "a contact ID via --id",
363
+ question: "Enter the contact ID:"
364
+ });
365
+ if (!await confirmOrAbort({
366
+ message: `Delete contact ${contactId}?`,
367
+ yes: options.yes,
368
+ initial: false
369
+ })) return;
370
+ await client.contacts.delete({ contactId });
371
+ if (options.json) {
372
+ printJson({ deleted: true });
373
+ return;
374
+ }
375
+ consola.success("Contact deleted.");
376
+ }
377
+ });
378
+ //#endregion
379
+ //#region src/commands/contacts/get.ts
380
+ var get_default$6 = defineCommand({
381
+ description: "Show a single contact.",
382
+ options: defineOptions(z.object({
383
+ id: z.coerce.number().optional().describe("The contact ID. If omitted, you will be prompted."),
384
+ json: z.boolean().default(false).describe("Output in JSON format.")
385
+ })),
386
+ action: async (options) => {
387
+ const client = await requireClient();
388
+ const contactId = await requireNumberOption({
389
+ value: options.id,
390
+ requirement: "a contact ID via --id",
391
+ question: "Enter the contact ID:"
392
+ });
393
+ const contact = await client.contacts.get({ contactId });
394
+ if (options.json) {
395
+ printJson(contact);
396
+ return;
397
+ }
398
+ consola.info(`ID: ${contact.id}`);
399
+ consola.info(`Name: ${contact.name ?? "-"}`);
400
+ consola.info(`First name: ${contact.surename ?? "-"}`);
401
+ consola.info(`Last name: ${contact.familyname ?? "-"}`);
402
+ consola.info(`Customer number: ${contact.customerNumber ?? "-"}`);
403
+ consola.info(`Category: ${contact.category.id}`);
404
+ consola.info(`Status: ${contact.status ?? "-"}`);
405
+ consola.info(`Description: ${contact.description ?? "-"}`);
406
+ consola.info(`VAT number: ${contact.vatNumber ?? "-"}`);
407
+ consola.info(`Tax number: ${contact.taxNumber ?? "-"}`);
408
+ }
409
+ });
410
+ //#endregion
411
+ //#region src/commands/contacts/list.ts
412
+ var list_default$7 = defineCommand({
413
+ description: "List contacts.",
414
+ options: defineOptions(z.object({
415
+ name: z.string().optional().describe("Filter by organization, first or last name."),
416
+ customerNumber: z.string().optional().describe("Filter by customer number."),
417
+ limit: z.coerce.number().optional().describe("Maximum number of contacts to return."),
418
+ offset: z.coerce.number().optional().describe("Number of contacts to skip."),
419
+ json: z.boolean().default(false).describe("Output in JSON format.")
420
+ })),
421
+ action: async (options) => {
422
+ const contacts = await (await requireClient()).contacts.list({
423
+ name: options.name,
424
+ customerNumber: options.customerNumber,
425
+ limit: options.limit,
426
+ offset: options.offset
427
+ });
428
+ if (options.json) {
429
+ printJson(contacts);
430
+ return;
431
+ }
432
+ if (contacts.length === 0) {
433
+ consola.info("No contacts found.");
434
+ return;
435
+ }
436
+ printTable(contacts.map((contact) => ({
437
+ id: contact.id,
438
+ number: contact.customerNumber ?? "",
439
+ name: formatContactName(contact),
440
+ status: contact.status ?? "",
441
+ category: contact.category.id
442
+ })));
443
+ }
444
+ });
445
+ function formatContactName(contact) {
446
+ return contact.name ?? [contact.surename, contact.familyname].filter(Boolean).join(" ");
447
+ }
448
+ //#endregion
449
+ //#region src/commands/contacts/update.ts
450
+ var update_default = defineCommand({
451
+ description: "Update a contact.",
452
+ options: defineOptions(z.object({
453
+ id: z.coerce.number().optional().describe("The contact ID. If omitted, you will be prompted."),
454
+ name: z.string().optional().describe("Name of the organization. Not to be used for persons."),
455
+ surename: z.string().optional().describe("First name of the person. Not to be used for organizations."),
456
+ familyname: z.string().optional().describe("Last name of the person. Not to be used for organizations."),
457
+ category: z.coerce.number().optional().describe("ID of the contact category: 2 supplier, 3 customer, 4 partner."),
458
+ customerNumber: z.string().optional().describe("Customer number of the contact."),
459
+ description: z.string().optional().describe("Description of the contact."),
460
+ vatNumber: z.string().optional().describe("VAT number of the contact."),
461
+ taxNumber: z.string().optional().describe("Tax number of the contact."),
462
+ json: z.boolean().default(false).describe("Output in JSON format.")
463
+ })),
464
+ action: async (options) => {
465
+ const client = await requireClient();
466
+ const contactId = await requireNumberOption({
467
+ value: options.id,
468
+ requirement: "a contact ID via --id",
469
+ question: "Enter the contact ID:"
470
+ });
471
+ const contact = await client.contacts.update({
472
+ contactId,
473
+ categoryId: options.category,
474
+ name: options.name,
475
+ surename: options.surename,
476
+ familyname: options.familyname,
477
+ customerNumber: options.customerNumber,
478
+ description: options.description,
479
+ vatNumber: options.vatNumber,
480
+ taxNumber: options.taxNumber
481
+ });
482
+ if (options.json) {
483
+ printJson(contact);
484
+ return;
485
+ }
486
+ consola.success(`Updated contact ${contact.id}.`);
487
+ }
488
+ });
489
+ //#endregion
490
+ //#region src/commands/credit-notes/get.ts
491
+ var get_default$5 = defineCommand({
492
+ description: "Show a single credit note.",
493
+ options: defineOptions(z.object({
494
+ id: z.coerce.number().optional().describe("ID of the credit note. If omitted, you will be prompted."),
495
+ json: z.boolean().default(false).describe("Output in JSON format.")
496
+ })),
497
+ action: async (options) => {
498
+ const client = await requireClient();
499
+ const creditNoteId = await requireNumberOption({
500
+ value: options.id,
501
+ requirement: "a credit note ID via --id",
502
+ question: "Enter the credit note ID:"
503
+ });
504
+ const creditNote = await client.creditNotes.get({ creditNoteId });
505
+ if (options.json) {
506
+ printJson(creditNote);
507
+ return;
508
+ }
509
+ consola.info(`ID: ${creditNote.id}`);
510
+ consola.info(`Number: ${creditNote.creditNoteNumber ?? "-"}`);
511
+ consola.info(`Date: ${creditNote.creditNoteDate}`);
512
+ consola.info(`Status: ${creditNote.status}`);
513
+ consola.info(`Contact: ${creditNote.contact?.id ?? "-"}`);
514
+ consola.info(`Net: ${creditNote.sumNet}`);
515
+ consola.info(`Tax: ${creditNote.sumTax}`);
516
+ consola.info(`Gross: ${creditNote.sumGross}`);
517
+ consola.info(`Currency: ${creditNote.currency ?? "-"}`);
518
+ }
519
+ });
520
+ //#endregion
521
+ //#region src/commands/credit-notes/list.ts
522
+ var list_default$6 = defineCommand({
523
+ description: "List credit notes.",
524
+ options: defineOptions(z.object({
525
+ status: z.coerce.number().optional().describe("Filter by status: 100 draft, 200 open, 750 partially paid, 1000 paid."),
526
+ creditNoteNumber: z.string().optional().describe("Filter by credit note number."),
527
+ startDate: z.string().optional().describe("Only credit notes on or after this date as dd.mm.yyyy or Unix timestamp."),
528
+ endDate: z.string().optional().describe("Only credit notes on or before this date as dd.mm.yyyy or Unix timestamp."),
529
+ contact: z.coerce.number().optional().describe("ID of the contact whose credit notes to list."),
530
+ limit: z.coerce.number().optional().describe("Maximum number of credit notes to return."),
531
+ offset: z.coerce.number().optional().describe("Number of credit notes to skip."),
532
+ json: z.boolean().default(false).describe("Output in JSON format.")
533
+ })),
534
+ action: async (options) => {
535
+ const creditNotes = await (await requireClient()).creditNotes.list({
536
+ status: options.status,
537
+ creditNoteNumber: options.creditNoteNumber,
538
+ startDate: options.startDate,
539
+ endDate: options.endDate,
540
+ contactId: options.contact,
541
+ limit: options.limit,
542
+ offset: options.offset
543
+ });
544
+ if (options.json) {
545
+ printJson(creditNotes);
546
+ return;
547
+ }
548
+ if (creditNotes.length === 0) {
549
+ consola.info("No credit notes found.");
550
+ return;
551
+ }
552
+ printTable(creditNotes.map((creditNote) => ({
553
+ id: creditNote.id,
554
+ number: creditNote.creditNoteNumber ?? "",
555
+ date: creditNote.creditNoteDate,
556
+ contact: creditNote.contact?.id ?? "",
557
+ gross: creditNote.sumGross,
558
+ status: creditNote.status
559
+ })));
560
+ }
561
+ });
562
+ //#endregion
563
+ //#region src/commands/credit-notes/pdf.ts
564
+ var pdf_default$2 = defineCommand({
565
+ description: "Download the PDF of a credit note.",
566
+ options: defineOptions(z.object({
567
+ id: z.coerce.number().optional().describe("ID of the credit note. If omitted, you will be prompted."),
568
+ output: z.string().optional().describe("Path to write the PDF to. Defaults to the file name reported by the API."),
569
+ preventSendBy: z.boolean().default(false).describe("Do not mark the credit note as sent by download."),
570
+ json: z.boolean().default(false).describe("Output in JSON format.")
571
+ })),
572
+ action: async (options) => {
573
+ const client = await requireClient();
574
+ const creditNoteId = await requireNumberOption({
575
+ value: options.id,
576
+ requirement: "a credit note ID via --id",
577
+ question: "Enter the credit note ID:"
578
+ });
579
+ const pdf = await client.creditNotes.getPdf({
580
+ creditNoteId,
581
+ preventSendBy: options.preventSendBy
582
+ });
583
+ const path = resolve(options.output ?? pdf.filename);
584
+ await writeFile(path, Buffer.from(pdf.content, "base64"));
585
+ if (options.json) {
586
+ printJson({
587
+ filename: pdf.filename,
588
+ path
589
+ });
590
+ return;
591
+ }
592
+ consola.success(`Saved ${path}.`);
593
+ }
594
+ });
595
+ //#endregion
596
+ //#region src/errors.ts
597
+ function formatError(error) {
598
+ if (error instanceof ZliError) return error.message;
599
+ if (error instanceof z.ZodError) return error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("\n");
600
+ if (error instanceof SevDeskError) {
601
+ if (error.status === 401) return "Authentication failed. Check your API token or run `sevdesk login` again.";
602
+ return error.message;
603
+ }
604
+ return error instanceof Error ? error.message : String(error);
605
+ }
606
+ //#endregion
607
+ //#region src/commands/doctor.ts
608
+ var doctor_default = defineCommand({
609
+ description: "Check the CLI setup and the connection to the sevdesk API.",
610
+ options: defineOptions(z.object({ json: z.boolean().default(false).describe("Output in JSON format.") })),
611
+ action: async (options) => {
612
+ const { token, source } = await resolveToken();
613
+ let bookkeepingSystemVersion;
614
+ let apiError;
615
+ if (token) try {
616
+ bookkeepingSystemVersion = await createClient(token).basics.getBookkeepingSystemVersion();
617
+ } catch (error) {
618
+ apiError = formatError(error);
619
+ }
620
+ if (options.json) printJson({
621
+ cliVersion: pkg.version,
622
+ nodeVersion: process.version,
623
+ tokenSource: source,
624
+ bookkeepingSystemVersion: bookkeepingSystemVersion ?? null,
625
+ apiError: apiError ?? null
626
+ });
627
+ else {
628
+ consola.info(`CLI version: ${pkg.version}`);
629
+ consola.info(`Node.js version: ${process.version}`);
630
+ consola.info(`Token source: ${source}`);
631
+ if (!token) consola.warn("No API token found. Run `sevdesk login` or set the SEVDESK_TOKEN environment variable.");
632
+ else if (apiError) consola.error(`API connection failed: ${apiError}`);
633
+ else consola.success(`API connection succeeded. Bookkeeping system version: ${bookkeepingSystemVersion}`);
634
+ }
635
+ if (bookkeepingSystemVersion === "1.0") {
636
+ consola.warn("This sevdesk account still uses bookkeeping system version 1.0, which is not supported.");
637
+ process.exitCode = 1;
638
+ } else if (!token || apiError) process.exitCode = 1;
639
+ }
640
+ });
641
+ //#endregion
642
+ //#region src/commands/export/contacts.ts
643
+ var contacts_default = defineCommand({
644
+ description: "Export contacts as a CSV file.",
645
+ options: defineOptions(z.object({
646
+ output: z.string().optional().describe("Path to write the CSV file to. Defaults to the filename of the export."),
647
+ limit: z.coerce.number().optional().describe("The maximum number of contacts to export."),
648
+ json: z.boolean().default(false).describe("Output in JSON format.")
649
+ })),
650
+ action: async (options) => {
651
+ const file = await (await requireClient()).exports.exportContactsCsv({ limit: options.limit });
652
+ const path = options.output ?? file.filename;
653
+ await writeFile(path, file.base64Encoded ? Buffer.from(file.content, "base64") : file.content);
654
+ if (options.json) {
655
+ printJson({
656
+ filename: file.filename,
657
+ path
658
+ });
659
+ return;
660
+ }
661
+ consola.success(`Saved ${path}.`);
662
+ }
663
+ });
664
+ //#endregion
665
+ //#region src/commands/export/datev.ts
666
+ const POLL_INTERVAL = 3e3;
667
+ var datev_default = defineCommand({
668
+ description: "Export accounting data in the DATEV format as a ZIP archive.",
669
+ options: defineOptions(z.object({
670
+ startDate: z.string().optional().describe("Start of the export period as dd.mm.yyyy or Unix timestamp. If omitted, you will be prompted."),
671
+ endDate: z.string().optional().describe("End of the export period as dd.mm.yyyy or Unix timestamp. If omitted, you will be prompted."),
672
+ format: z.enum(["csv", "xml"]).default("csv").describe("The DATEV export format. Supported values are csv and xml."),
673
+ scope: z.string().default("EXTCD").describe("The models to include as a string of letters: E (earnings), X (expenditure), T (transactions), C (cash register) and D (assets). XML exports support only E and X."),
674
+ output: z.string().optional().describe("Path to write the ZIP archive to. Defaults to the filename of the export."),
675
+ timeout: z.coerce.number().default(300).describe("Maximum number of seconds to wait for the export job."),
676
+ json: z.boolean().default(false).describe("Output in JSON format.")
677
+ })),
678
+ action: async (options) => {
679
+ const client = await requireClient();
680
+ const jobOptions = {
681
+ startDate: await requireStringOption({
682
+ value: options.startDate,
683
+ requirement: "a start date via --start-date",
684
+ question: "Enter the start date (dd.mm.yyyy):"
685
+ }),
686
+ endDate: await requireStringOption({
687
+ value: options.endDate,
688
+ requirement: "an end date via --end-date",
689
+ question: "Enter the end date (dd.mm.yyyy):"
690
+ }),
691
+ scope: options.scope
692
+ };
693
+ const jobId = options.format === "csv" ? await client.exports.createDatevCsvZipExportJob(jobOptions) : await client.exports.createDatevXmlZipExportJob(jobOptions);
694
+ if (!options.json) consola.start("Waiting for the export job to finish...");
695
+ const info = await waitForDownloadInfo(client, jobId, options.timeout);
696
+ if (!info) {
697
+ consola.error(`The export job did not finish within ${options.timeout} seconds.`);
698
+ process.exit(1);
699
+ }
700
+ const response = await fetch(info.link);
701
+ if (!response.ok) {
702
+ consola.error(`The export file could not be downloaded (status ${response.status}).`);
703
+ process.exit(1);
704
+ }
705
+ const path = options.output ?? info.filename;
706
+ await writeFile(path, new Uint8Array(await response.arrayBuffer()));
707
+ if (options.json) {
708
+ printJson({
709
+ jobId,
710
+ filename: info.filename,
711
+ path
712
+ });
713
+ return;
714
+ }
715
+ consola.success(`Saved ${path}.`);
716
+ }
717
+ });
718
+ async function waitForDownloadInfo(client, jobId, timeoutSeconds) {
719
+ const deadline = Date.now() + timeoutSeconds * 1e3;
720
+ while (Date.now() < deadline) {
721
+ const info = await getDownloadInfo(client, jobId);
722
+ if (info) return info;
723
+ await delay(POLL_INTERVAL);
724
+ }
725
+ }
726
+ async function getDownloadInfo(client, jobId) {
727
+ try {
728
+ const [info] = await client.exports.getJobDownloadInfo({ jobId });
729
+ return info?.link ? info : void 0;
730
+ } catch (error) {
731
+ if (error instanceof SevDeskError) return;
732
+ throw error;
733
+ }
734
+ }
735
+ function delay(milliseconds) {
736
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
737
+ }
738
+ //#endregion
739
+ //#region src/commands/export/invoices.ts
740
+ var invoices_default = defineCommand({
741
+ description: "Export invoices as a CSV file.",
742
+ options: defineOptions(z.object({
743
+ output: z.string().optional().describe("Path to write the CSV file to. Defaults to the filename of the export."),
744
+ limit: z.coerce.number().optional().describe("The maximum number of invoices to export."),
745
+ startDate: z.string().optional().describe("Only export invoices on or after this date (ISO 8601)."),
746
+ endDate: z.string().optional().describe("Only export invoices on or before this date (ISO 8601)."),
747
+ json: z.boolean().default(false).describe("Output in JSON format.")
748
+ })),
749
+ action: async (options) => {
750
+ const file = await (await requireClient()).exports.exportInvoicesCsv({
751
+ limit: options.limit,
752
+ filter: {
753
+ startDate: options.startDate,
754
+ endDate: options.endDate
755
+ }
756
+ });
757
+ const path = options.output ?? file.filename;
758
+ await writeFile(path, file.base64Encoded ? Buffer.from(file.content, "base64") : file.content);
759
+ if (options.json) {
760
+ printJson({
761
+ filename: file.filename,
762
+ path
763
+ });
764
+ return;
765
+ }
766
+ consola.success(`Saved ${path}.`);
767
+ }
768
+ });
769
+ //#endregion
770
+ //#region src/commands/export/transactions.ts
771
+ var transactions_default = defineCommand({
772
+ description: "Export transactions as a CSV file.",
773
+ options: defineOptions(z.object({
774
+ output: z.string().optional().describe("Path to write the CSV file to. Defaults to the filename of the export."),
775
+ limit: z.coerce.number().optional().describe("The maximum number of transactions to export."),
776
+ startDate: z.string().optional().describe("Only export transactions on or after this date (ISO 8601)."),
777
+ endDate: z.string().optional().describe("Only export transactions on or before this date (ISO 8601)."),
778
+ json: z.boolean().default(false).describe("Output in JSON format.")
779
+ })),
780
+ action: async (options) => {
781
+ const file = await (await requireClient()).exports.exportTransactionsCsv({
782
+ limit: options.limit,
783
+ filter: {
784
+ startDate: options.startDate,
785
+ endDate: options.endDate
786
+ }
787
+ });
788
+ const path = options.output ?? file.filename;
789
+ await writeFile(path, file.base64Encoded ? Buffer.from(file.content, "base64") : file.content);
790
+ if (options.json) {
791
+ printJson({
792
+ filename: file.filename,
793
+ path
794
+ });
795
+ return;
796
+ }
797
+ consola.success(`Saved ${path}.`);
798
+ }
799
+ });
800
+ //#endregion
801
+ //#region src/commands/export/vouchers.ts
802
+ var vouchers_default = defineCommand({
803
+ description: "Export vouchers as a CSV file.",
804
+ options: defineOptions(z.object({
805
+ output: z.string().optional().describe("Path to write the CSV file to. Defaults to the filename of the export."),
806
+ limit: z.coerce.number().optional().describe("The maximum number of vouchers to export."),
807
+ startDate: z.string().optional().describe("Only export vouchers on or after this date (ISO 8601)."),
808
+ endDate: z.string().optional().describe("Only export vouchers on or before this date (ISO 8601)."),
809
+ json: z.boolean().default(false).describe("Output in JSON format.")
810
+ })),
811
+ action: async (options) => {
812
+ const file = await (await requireClient()).exports.exportVouchersCsv({
813
+ limit: options.limit,
814
+ filter: {
815
+ startDate: options.startDate,
816
+ endDate: options.endDate
817
+ }
818
+ });
819
+ const path = options.output ?? file.filename;
820
+ await writeFile(path, file.base64Encoded ? Buffer.from(file.content, "base64") : file.content);
821
+ if (options.json) {
822
+ printJson({
823
+ filename: file.filename,
824
+ path
825
+ });
826
+ return;
827
+ }
828
+ consola.success(`Saved ${path}.`);
829
+ }
830
+ });
831
+ //#endregion
832
+ //#region src/commands/guidance/accounts.ts
833
+ const MAX_TAX_RULES = 3;
834
+ function formatTaxRules(taxRules) {
835
+ const names = taxRules.map((taxRule) => taxRule.name);
836
+ if (names.length > MAX_TAX_RULES) return `${names.slice(0, MAX_TAX_RULES).join(", ")}, …`;
837
+ return names.join(", ");
838
+ }
839
+ var accounts_default = defineCommand({
840
+ description: "List bookable accounts and their allowed tax rules. The first matching filter wins: --account-number, --tax-rule, --revenue, --expense. Without a filter, all accounts are listed.",
841
+ options: defineOptions(z.object({
842
+ accountNumber: z.string().optional().describe("Only show the account with this datev account number."),
843
+ taxRule: z.string().optional().describe("Tax rule name, for example USTPFL_UMS_EINN."),
844
+ revenue: z.boolean().default(false).describe("Only show accounts that can be used for revenue."),
845
+ expense: z.boolean().default(false).describe("Only show accounts that can be used for expenses."),
846
+ json: z.boolean().default(false).describe("Output in JSON format.")
847
+ })),
848
+ action: async (options) => {
849
+ const client = await requireClient();
850
+ let guides;
851
+ if (options.accountNumber) guides = await client.receiptGuidance.forAccountNumber({ accountNumber: options.accountNumber });
852
+ else if (options.taxRule) guides = await client.receiptGuidance.forTaxRule({ taxRule: options.taxRule });
853
+ else if (options.revenue) guides = await client.receiptGuidance.forRevenue();
854
+ else if (options.expense) guides = await client.receiptGuidance.forExpense();
855
+ else guides = await client.receiptGuidance.forAllAccounts();
856
+ if (options.json) {
857
+ printJson(guides);
858
+ return;
859
+ }
860
+ if (guides.length === 0) {
861
+ consola.info("No accounts found.");
862
+ return;
863
+ }
864
+ printTable(guides.map((guide) => ({
865
+ id: String(guide.accountDatevId),
866
+ number: guide.accountNumber,
867
+ name: guide.accountName,
868
+ taxRules: formatTaxRules(guide.allowedTaxRules)
869
+ })));
870
+ }
871
+ });
872
+ //#endregion
873
+ //#region src/commands/invoices/get.ts
874
+ var get_default$4 = defineCommand({
875
+ description: "Show a single invoice.",
876
+ options: defineOptions(z.object({
877
+ id: z.coerce.number().optional().describe("ID of the invoice. If omitted, you will be prompted."),
878
+ json: z.boolean().default(false).describe("Output in JSON format.")
879
+ })),
880
+ action: async (options) => {
881
+ const client = await requireClient();
882
+ const invoiceId = await requireNumberOption({
883
+ value: options.id,
884
+ requirement: "an invoice ID via --id",
885
+ question: "Enter the invoice ID:"
886
+ });
887
+ const invoice = await client.invoices.get({ invoiceId });
888
+ if (options.json) {
889
+ printJson(invoice);
890
+ return;
891
+ }
892
+ consola.info(`ID: ${invoice.id}`);
893
+ consola.info(`Number: ${invoice.invoiceNumber ?? "-"}`);
894
+ consola.info(`Date: ${invoice.invoiceDate}`);
895
+ consola.info(`Status: ${invoice.status}`);
896
+ consola.info(`Contact: ${invoice.contact?.id ?? "-"}`);
897
+ consola.info(`Net: ${invoice.sumNet}`);
898
+ consola.info(`Tax: ${invoice.sumTax}`);
899
+ consola.info(`Gross: ${invoice.sumGross}`);
900
+ consola.info(`Currency: ${invoice.currency}`);
901
+ }
902
+ });
903
+ //#endregion
904
+ //#region src/commands/invoices/list.ts
905
+ var list_default$5 = defineCommand({
906
+ description: "List invoices.",
907
+ options: defineOptions(z.object({
908
+ status: z.coerce.number().optional().describe("Filter by status: 50 deactivated recurring, 100 draft, 200 open, 750 partially paid, 1000 paid."),
909
+ invoiceNumber: z.string().optional().describe("Filter by invoice number."),
910
+ startDate: z.string().optional().describe("Only invoices on or after this date as dd.mm.yyyy or Unix timestamp."),
911
+ endDate: z.string().optional().describe("Only invoices on or before this date as dd.mm.yyyy or Unix timestamp."),
912
+ contact: z.coerce.number().optional().describe("ID of the contact whose invoices to list."),
913
+ limit: z.coerce.number().optional().describe("Maximum number of invoices to return."),
914
+ offset: z.coerce.number().optional().describe("Number of invoices to skip."),
915
+ json: z.boolean().default(false).describe("Output in JSON format.")
916
+ })),
917
+ action: async (options) => {
918
+ const invoices = await (await requireClient()).invoices.list({
919
+ status: options.status,
920
+ invoiceNumber: options.invoiceNumber,
921
+ startDate: options.startDate,
922
+ endDate: options.endDate,
923
+ contactId: options.contact,
924
+ limit: options.limit,
925
+ offset: options.offset
926
+ });
927
+ if (options.json) {
928
+ printJson(invoices);
929
+ return;
930
+ }
931
+ if (invoices.length === 0) {
932
+ consola.info("No invoices found.");
933
+ return;
934
+ }
935
+ printTable(invoices.map((invoice) => ({
936
+ id: invoice.id,
937
+ number: invoice.invoiceNumber ?? "",
938
+ date: invoice.invoiceDate,
939
+ contact: invoice.contact?.id ?? "",
940
+ gross: invoice.sumGross,
941
+ status: invoice.status
942
+ })));
943
+ }
944
+ });
945
+ //#endregion
946
+ //#region src/commands/invoices/pdf.ts
947
+ var pdf_default$1 = defineCommand({
948
+ description: "Download the PDF of an invoice.",
949
+ options: defineOptions(z.object({
950
+ id: z.coerce.number().optional().describe("ID of the invoice. If omitted, you will be prompted."),
951
+ output: z.string().optional().describe("Path to write the PDF to. Defaults to the file name reported by the API."),
952
+ preventSendBy: z.boolean().default(false).describe("Do not mark the invoice as sent by download."),
953
+ json: z.boolean().default(false).describe("Output in JSON format.")
954
+ })),
955
+ action: async (options) => {
956
+ const client = await requireClient();
957
+ const invoiceId = await requireNumberOption({
958
+ value: options.id,
959
+ requirement: "an invoice ID via --id",
960
+ question: "Enter the invoice ID:"
961
+ });
962
+ const pdf = await client.invoices.getPdf({
963
+ invoiceId,
964
+ preventSendBy: options.preventSendBy
965
+ });
966
+ const path = resolve(options.output ?? pdf.filename);
967
+ await writeFile(path, Buffer.from(pdf.content, "base64"));
968
+ if (options.json) {
969
+ printJson({
970
+ filename: pdf.filename,
971
+ path
972
+ });
973
+ return;
974
+ }
975
+ consola.success(`Saved ${path}.`);
976
+ }
977
+ });
978
+ //#endregion
979
+ //#region src/commands/login.ts
980
+ var login_default = defineCommand({
981
+ description: "Log in with your sevdesk API token.",
982
+ options: defineOptions(z.object({ token: z.string().optional().describe("The sevdesk API token. If omitted, you will be prompted.") })),
983
+ action: async (options) => {
984
+ let token = options.token?.trim();
985
+ if (!token) {
986
+ if (!isInteractive()) {
987
+ consola.error("You must provide a token via --token when running in a non-interactive environment.");
988
+ process.exit(1);
989
+ }
990
+ consola.info("You can find your API token in sevdesk under Settings > User > API token.");
991
+ token = (await promptText("Enter your sevdesk API token:")).trim();
992
+ }
993
+ if (await createClient(token).basics.getBookkeepingSystemVersion() === "1.0") {
994
+ consola.error("This sevdesk account still uses bookkeeping system version 1.0, which is not supported. Please update your account to sevdesk-Update 2.0.");
995
+ process.exit(1);
996
+ }
997
+ await writeConfig({
998
+ ...await readConfig(),
999
+ token
1000
+ });
1001
+ consola.success(`Logged in. Token stored in ${configPath()}.`);
1002
+ }
1003
+ });
1004
+ //#endregion
1005
+ //#region src/commands/logout.ts
1006
+ var logout_default = defineCommand({
1007
+ description: "Log out by removing the stored API token.",
1008
+ action: async () => {
1009
+ const { token, ...config } = await readConfig();
1010
+ if (!token) consola.info("You are not logged in.");
1011
+ else {
1012
+ await writeConfig(config);
1013
+ consola.success("Logged out.");
1014
+ }
1015
+ if (process.env["SEVDESK_TOKEN"]) consola.warn("The SEVDESK_TOKEN environment variable is still set and will be used for authentication.");
1016
+ }
1017
+ });
1018
+ //#endregion
1019
+ //#region src/commands/orders/get.ts
1020
+ var get_default$3 = defineCommand({
1021
+ description: "Show a single order.",
1022
+ options: defineOptions(z.object({
1023
+ id: z.coerce.number().optional().describe("ID of the order. If omitted, you will be prompted."),
1024
+ json: z.boolean().default(false).describe("Output in JSON format.")
1025
+ })),
1026
+ action: async (options) => {
1027
+ const client = await requireClient();
1028
+ const orderId = await requireNumberOption({
1029
+ value: options.id,
1030
+ requirement: "an order ID via --id",
1031
+ question: "Enter the order ID:"
1032
+ });
1033
+ const order = await client.orders.get({ orderId });
1034
+ if (options.json) {
1035
+ printJson(order);
1036
+ return;
1037
+ }
1038
+ consola.info(`ID: ${order.id}`);
1039
+ consola.info(`Number: ${order.orderNumber}`);
1040
+ consola.info(`Date: ${order.orderDate}`);
1041
+ consola.info(`Status: ${order.status}`);
1042
+ consola.info(`Contact: ${order.contact?.id ?? "-"}`);
1043
+ consola.info(`Net: ${order.sumNet}`);
1044
+ consola.info(`Tax: ${order.sumTax}`);
1045
+ consola.info(`Gross: ${order.sumGross}`);
1046
+ consola.info(`Currency: ${order.currency}`);
1047
+ }
1048
+ });
1049
+ //#endregion
1050
+ //#region src/commands/orders/list.ts
1051
+ var list_default$4 = defineCommand({
1052
+ description: "List orders.",
1053
+ options: defineOptions(z.object({
1054
+ status: z.coerce.number().optional().describe("Filter by status: 100 draft, 200 delivered, 300 rejected, 500 accepted, 750 partially calculated, 1000 calculated."),
1055
+ orderNumber: z.string().optional().describe("Filter by order number."),
1056
+ startDate: z.string().optional().describe("Only orders on or after this date as dd.mm.yyyy or Unix timestamp."),
1057
+ endDate: z.string().optional().describe("Only orders on or before this date as dd.mm.yyyy or Unix timestamp."),
1058
+ contact: z.coerce.number().optional().describe("ID of the contact whose orders to list."),
1059
+ limit: z.coerce.number().optional().describe("Maximum number of orders to return."),
1060
+ offset: z.coerce.number().optional().describe("Number of orders to skip."),
1061
+ json: z.boolean().default(false).describe("Output in JSON format.")
1062
+ })),
1063
+ action: async (options) => {
1064
+ const orders = await (await requireClient()).orders.list({
1065
+ status: options.status,
1066
+ orderNumber: options.orderNumber,
1067
+ startDate: options.startDate,
1068
+ endDate: options.endDate,
1069
+ contactId: options.contact,
1070
+ limit: options.limit,
1071
+ offset: options.offset
1072
+ });
1073
+ if (options.json) {
1074
+ printJson(orders);
1075
+ return;
1076
+ }
1077
+ if (orders.length === 0) {
1078
+ consola.info("No orders found.");
1079
+ return;
1080
+ }
1081
+ printTable(orders.map((order) => ({
1082
+ id: order.id,
1083
+ number: order.orderNumber,
1084
+ date: order.orderDate,
1085
+ contact: order.contact?.id ?? "",
1086
+ gross: order.sumGross,
1087
+ status: order.status
1088
+ })));
1089
+ }
1090
+ });
1091
+ //#endregion
1092
+ //#region src/commands/orders/pdf.ts
1093
+ var pdf_default = defineCommand({
1094
+ description: "Download the PDF of an order.",
1095
+ options: defineOptions(z.object({
1096
+ id: z.coerce.number().optional().describe("ID of the order. If omitted, you will be prompted."),
1097
+ output: z.string().optional().describe("Path to write the PDF to. Defaults to the file name reported by the API."),
1098
+ preventSendBy: z.boolean().default(false).describe("Do not mark the order as sent by download."),
1099
+ json: z.boolean().default(false).describe("Output in JSON format.")
1100
+ })),
1101
+ action: async (options) => {
1102
+ const client = await requireClient();
1103
+ const orderId = await requireNumberOption({
1104
+ value: options.id,
1105
+ requirement: "an order ID via --id",
1106
+ question: "Enter the order ID:"
1107
+ });
1108
+ const pdf = await client.orders.getPdf({
1109
+ orderId,
1110
+ preventSendBy: options.preventSendBy
1111
+ });
1112
+ const path = resolve(options.output ?? pdf.filename);
1113
+ await writeFile(path, Buffer.from(pdf.content, "base64"));
1114
+ if (options.json) {
1115
+ printJson({
1116
+ filename: pdf.filename,
1117
+ path
1118
+ });
1119
+ return;
1120
+ }
1121
+ consola.success(`Saved ${path}.`);
1122
+ }
1123
+ });
1124
+ //#endregion
1125
+ //#region src/commands/parts/get.ts
1126
+ var get_default$2 = defineCommand({
1127
+ description: "Show a single part.",
1128
+ options: defineOptions(z.object({
1129
+ id: z.coerce.number().optional().describe("The part ID. If omitted, you will be prompted."),
1130
+ json: z.boolean().default(false).describe("Output in JSON format.")
1131
+ })),
1132
+ action: async (options) => {
1133
+ const client = await requireClient();
1134
+ const partId = await requireNumberOption({
1135
+ value: options.id,
1136
+ requirement: "a part ID via --id",
1137
+ question: "Enter the part ID:"
1138
+ });
1139
+ const part = await client.parts.get({ partId });
1140
+ if (options.json) {
1141
+ printJson(part);
1142
+ return;
1143
+ }
1144
+ consola.info(`ID: ${part.id}`);
1145
+ consola.info(`Part number: ${part.partNumber}`);
1146
+ consola.info(`Name: ${part.name}`);
1147
+ consola.info(`Text: ${part.text ?? "-"}`);
1148
+ consola.info(`Price: ${part.price ?? "-"}`);
1149
+ consola.info(`Price net: ${part.priceNet ?? "-"}`);
1150
+ consola.info(`Price gross: ${part.priceGross ?? "-"}`);
1151
+ consola.info(`Tax rate: ${part.taxRate}`);
1152
+ consola.info(`Stock: ${part.stock}`);
1153
+ consola.info(`Status: ${part.status ?? "-"}`);
1154
+ }
1155
+ });
1156
+ //#endregion
1157
+ //#region src/commands/parts/list.ts
1158
+ var list_default$3 = defineCommand({
1159
+ description: "List parts.",
1160
+ options: defineOptions(z.object({
1161
+ name: z.string().optional().describe("Filter by part name."),
1162
+ partNumber: z.string().optional().describe("Filter by part number."),
1163
+ limit: z.coerce.number().optional().describe("Maximum number of parts to return."),
1164
+ offset: z.coerce.number().optional().describe("Number of parts to skip."),
1165
+ json: z.boolean().default(false).describe("Output in JSON format.")
1166
+ })),
1167
+ action: async (options) => {
1168
+ const parts = await (await requireClient()).parts.list({
1169
+ name: options.name,
1170
+ partNumber: options.partNumber,
1171
+ limit: options.limit,
1172
+ offset: options.offset
1173
+ });
1174
+ if (options.json) {
1175
+ printJson(parts);
1176
+ return;
1177
+ }
1178
+ if (parts.length === 0) {
1179
+ consola.info("No parts found.");
1180
+ return;
1181
+ }
1182
+ printTable(parts.map((part) => ({
1183
+ id: part.id,
1184
+ partNumber: part.partNumber,
1185
+ name: part.name,
1186
+ price: part.price ?? "",
1187
+ taxRate: part.taxRate,
1188
+ status: part.status ?? "",
1189
+ stock: part.stock
1190
+ })));
1191
+ }
1192
+ });
1193
+ //#endregion
1194
+ //#region src/commands/tags/create.ts
1195
+ const OBJECT_TYPES = [
1196
+ "Invoice",
1197
+ "Voucher",
1198
+ "Order",
1199
+ "CreditNote"
1200
+ ];
1201
+ var create_default$2 = defineCommand({
1202
+ description: "Create a tag and attach it to a document.",
1203
+ options: defineOptions(z.object({
1204
+ name: z.string().optional().describe("Name of the tag. If omitted, you will be prompted."),
1205
+ objectType: z.string().optional().describe("Type of the document to tag: Invoice, Voucher, Order or CreditNote. If omitted, you will be prompted."),
1206
+ objectId: z.coerce.number().optional().describe("ID of the document to tag. If omitted, you will be prompted."),
1207
+ json: z.boolean().default(false).describe("Output in JSON format.")
1208
+ })),
1209
+ action: async (options) => {
1210
+ const client = await requireClient();
1211
+ const name = await requireStringOption({
1212
+ value: options.name,
1213
+ requirement: "a tag name via --name",
1214
+ question: "Enter the tag name:"
1215
+ });
1216
+ const objectType = await requireStringOption({
1217
+ value: options.objectType,
1218
+ requirement: "a document type via --object-type",
1219
+ question: `Enter the document type (${OBJECT_TYPES.join(", ")}):`
1220
+ });
1221
+ if (!isTagObjectType(objectType)) {
1222
+ consola.error(`The document type must be one of: ${OBJECT_TYPES.join(", ")}.`);
1223
+ process.exit(1);
1224
+ }
1225
+ const objectId = await requireNumberOption({
1226
+ value: options.objectId,
1227
+ requirement: "a document ID via --object-id",
1228
+ question: "Enter the document ID:"
1229
+ });
1230
+ const relation = await client.tags.create({
1231
+ name,
1232
+ objectId,
1233
+ objectName: objectType
1234
+ });
1235
+ if (options.json) {
1236
+ printJson(relation);
1237
+ return;
1238
+ }
1239
+ consola.success(`Created tag ${relation.tag.id}.`);
1240
+ }
1241
+ });
1242
+ function isTagObjectType(value) {
1243
+ return OBJECT_TYPES.some((objectType) => objectType === value);
1244
+ }
1245
+ //#endregion
1246
+ //#region src/commands/tags/delete.ts
1247
+ var delete_default = defineCommand({
1248
+ description: "Delete a tag.",
1249
+ options: defineOptions(z.object({
1250
+ id: z.coerce.number().optional().describe("The tag ID. If omitted, you will be prompted."),
1251
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
1252
+ json: z.boolean().default(false).describe("Output in JSON format.")
1253
+ })),
1254
+ action: async (options) => {
1255
+ const client = await requireClient();
1256
+ const tagId = await requireNumberOption({
1257
+ value: options.id,
1258
+ requirement: "a tag ID via --id",
1259
+ question: "Enter the tag ID:"
1260
+ });
1261
+ if (!await confirmOrAbort({
1262
+ message: `Delete tag ${tagId}?`,
1263
+ yes: options.yes,
1264
+ initial: false
1265
+ })) return;
1266
+ await client.tags.delete({ tagId });
1267
+ if (options.json) {
1268
+ printJson({ deleted: true });
1269
+ return;
1270
+ }
1271
+ consola.success("Tag deleted.");
1272
+ }
1273
+ });
1274
+ //#endregion
1275
+ //#region src/commands/tags/list.ts
1276
+ var list_default$2 = defineCommand({
1277
+ description: "List tags.",
1278
+ options: defineOptions(z.object({
1279
+ limit: z.coerce.number().optional().describe("Maximum number of tags to return."),
1280
+ offset: z.coerce.number().optional().describe("Number of tags to skip."),
1281
+ json: z.boolean().default(false).describe("Output in JSON format.")
1282
+ })),
1283
+ action: async (options) => {
1284
+ const tags = await (await requireClient()).tags.list({
1285
+ limit: options.limit,
1286
+ offset: options.offset
1287
+ });
1288
+ if (options.json) {
1289
+ printJson(tags);
1290
+ return;
1291
+ }
1292
+ if (tags.length === 0) {
1293
+ consola.info("No tags found.");
1294
+ return;
1295
+ }
1296
+ printTable(tags.map((tag) => ({
1297
+ id: tag.id,
1298
+ name: tag.name
1299
+ })));
1300
+ }
1301
+ });
1302
+ //#endregion
1303
+ //#region src/commands/transactions/create.ts
1304
+ /**
1305
+ * Creates a transaction manually. This should only be used on check accounts
1306
+ * of type online, preferably on accounts that import their transactions from
1307
+ * files, because transactions of connected bank accounts are synchronized by
1308
+ * sevdesk itself.
1309
+ */
1310
+ var create_default$1 = defineCommand({
1311
+ description: "Create a transaction. Only use this on file import (online) check accounts.",
1312
+ options: defineOptions(z.object({
1313
+ checkAccount: z.coerce.number().optional().describe("The ID of the check account the transaction belongs to. If omitted, you will be prompted."),
1314
+ amount: z.coerce.number().optional().describe("The amount of the transaction. Negative for expenses. If omitted, you will be prompted."),
1315
+ payee: z.string().optional().describe("The name of the payee or payer. If omitted, you will be prompted."),
1316
+ valueDate: z.string().default(() => (/* @__PURE__ */ new Date()).toISOString()).describe("The date the transaction was booked (ISO 8601)."),
1317
+ entryDate: z.string().optional().describe("The date the transaction was imported (ISO 8601)."),
1318
+ purpose: z.string().optional().describe("The payment purpose of the transaction."),
1319
+ status: z.coerce.number().default(100).describe("100 created, 200 linked, 300 private, 400 booked."),
1320
+ json: z.boolean().default(false).describe("Output in JSON format.")
1321
+ })),
1322
+ action: async (options) => {
1323
+ const client = await requireClient();
1324
+ const checkAccountId = await requireNumberOption({
1325
+ value: options.checkAccount,
1326
+ requirement: "a check account ID via --check-account",
1327
+ question: "Enter the check account ID:"
1328
+ });
1329
+ const amount = await requireNumberOption({
1330
+ value: options.amount,
1331
+ requirement: "an amount via --amount",
1332
+ question: "Enter the amount:"
1333
+ });
1334
+ const payee = await requireStringOption({
1335
+ value: options.payee,
1336
+ requirement: "a payee via --payee",
1337
+ question: "Enter the payee or payer name:"
1338
+ });
1339
+ const transaction = await client.transactions.create({
1340
+ checkAccountId,
1341
+ amount,
1342
+ payeePayerName: payee,
1343
+ valueDate: options.valueDate,
1344
+ entryDate: options.entryDate,
1345
+ paymtPurpose: options.purpose,
1346
+ status: options.status
1347
+ });
1348
+ if (options.json) {
1349
+ printJson(transaction);
1350
+ return;
1351
+ }
1352
+ consola.success(`Created transaction ${transaction.id}.`);
1353
+ }
1354
+ });
1355
+ //#endregion
1356
+ //#region src/commands/transactions/get.ts
1357
+ var get_default$1 = defineCommand({
1358
+ description: "Show a single transaction.",
1359
+ options: defineOptions(z.object({
1360
+ id: z.coerce.number().optional().describe("The transaction ID. If omitted, you will be prompted."),
1361
+ json: z.boolean().default(false).describe("Output in JSON format.")
1362
+ })),
1363
+ action: async (options) => {
1364
+ const client = await requireClient();
1365
+ const transactionId = await requireNumberOption({
1366
+ value: options.id,
1367
+ requirement: "a transaction ID via --id",
1368
+ question: "Enter the transaction ID:"
1369
+ });
1370
+ const transaction = await client.transactions.get({ transactionId });
1371
+ if (options.json) {
1372
+ printJson(transaction);
1373
+ return;
1374
+ }
1375
+ consola.info(`ID: ${transaction.id}`);
1376
+ consola.info(`Value date: ${transaction.valueDate}`);
1377
+ consola.info(`Amount: ${transaction.amount}`);
1378
+ consola.info(`Payee: ${transaction.payeePayerName ?? "-"}`);
1379
+ consola.info(`Purpose: ${transaction.paymtPurpose ?? "-"}`);
1380
+ consola.info(`Status: ${transaction.status}`);
1381
+ consola.info(`Check account: ${transaction.checkAccount.id}`);
1382
+ }
1383
+ });
1384
+ //#endregion
1385
+ //#region src/commands/transactions/list.ts
1386
+ var list_default$1 = defineCommand({
1387
+ description: "List transactions of your check accounts.",
1388
+ options: defineOptions(z.object({
1389
+ checkAccount: z.coerce.number().optional().describe("Only show transactions of this check account ID."),
1390
+ unbooked: z.boolean().default(false).describe("Only show transactions that are not yet booked."),
1391
+ startDate: z.string().optional().describe("Only show transactions on or after this date (ISO 8601)."),
1392
+ endDate: z.string().optional().describe("Only show transactions on or before this date (ISO 8601)."),
1393
+ payee: z.string().optional().describe("Only show transactions with this payee or payer name."),
1394
+ purpose: z.string().optional().describe("Only show transactions with this payment purpose."),
1395
+ limit: z.coerce.number().optional().describe("The maximum number of transactions to return."),
1396
+ offset: z.coerce.number().optional().describe("The number of transactions to skip."),
1397
+ json: z.boolean().default(false).describe("Output in JSON format.")
1398
+ })),
1399
+ action: async (options) => {
1400
+ const transactions = await (await requireClient()).transactions.list({
1401
+ checkAccountId: options.checkAccount,
1402
+ isBooked: options.unbooked ? false : void 0,
1403
+ startDate: options.startDate,
1404
+ endDate: options.endDate,
1405
+ payeePayerName: options.payee,
1406
+ paymtPurpose: options.purpose,
1407
+ limit: options.limit,
1408
+ offset: options.offset
1409
+ });
1410
+ if (options.json) {
1411
+ printJson(transactions);
1412
+ return;
1413
+ }
1414
+ if (transactions.length === 0) {
1415
+ consola.info("No transactions found.");
1416
+ return;
1417
+ }
1418
+ printTable(transactions.map((transaction) => ({
1419
+ id: transaction.id,
1420
+ valueDate: transaction.valueDate,
1421
+ payee: transaction.payeePayerName ?? "",
1422
+ purpose: transaction.paymtPurpose ?? "",
1423
+ amount: transaction.amount,
1424
+ status: transaction.status
1425
+ })));
1426
+ }
1427
+ });
1428
+ //#endregion
1429
+ //#region src/commands/vouchers/book.ts
1430
+ var book_default = defineCommand({
1431
+ description: "Book a payment on a voucher.",
1432
+ options: defineOptions(z.object({
1433
+ id: z.coerce.number().optional().describe("ID of the voucher."),
1434
+ amount: z.coerce.number().optional().describe("Amount to book. Can also be a partial amount."),
1435
+ date: z.string().default(() => (/* @__PURE__ */ new Date()).toISOString()).describe("Booking date as ISO 8601. Defaults to now."),
1436
+ type: z.enum([
1437
+ "FULL_PAYMENT",
1438
+ "N",
1439
+ "CB",
1440
+ "O",
1441
+ "OF",
1442
+ "MTC"
1443
+ ]).default("FULL_PAYMENT").describe("Type of the booking. Supported values are FULL_PAYMENT, N (partial), CB (cash discount), O (other), OF (reminder charges) and MTC (monetary traffic costs)."),
1444
+ checkAccount: z.coerce.number().optional().describe("ID of the check account."),
1445
+ transaction: z.coerce.number().optional().describe("ID of the bank transaction to link. Required for online check accounts, omit for clearing accounts."),
1446
+ createFeed: z.boolean().optional().describe("Create a feed entry for the booking."),
1447
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
1448
+ json: z.boolean().default(false).describe("Output in JSON format.")
1449
+ })),
1450
+ action: async (options) => {
1451
+ const client = await requireClient();
1452
+ const voucherId = await requireNumberOption({
1453
+ value: options.id,
1454
+ requirement: "a voucher via --id",
1455
+ question: "Enter the voucher ID:"
1456
+ });
1457
+ const amount = await requireNumberOption({
1458
+ value: options.amount,
1459
+ requirement: "an amount via --amount",
1460
+ question: "Enter the amount to book:"
1461
+ });
1462
+ const checkAccountId = await requireNumberOption({
1463
+ value: options.checkAccount,
1464
+ requirement: "a check account via --check-account",
1465
+ question: "Enter the check account ID:"
1466
+ });
1467
+ if (!await confirmOrAbort({
1468
+ message: `Book ${amount} on voucher ${voucherId}?`,
1469
+ yes: options.yes
1470
+ })) return;
1471
+ const result = await client.vouchers.book({
1472
+ voucherId,
1473
+ amount,
1474
+ date: options.date,
1475
+ type: options.type,
1476
+ checkAccountId,
1477
+ checkAccountTransactionId: options.transaction,
1478
+ createFeed: options.createFeed
1479
+ });
1480
+ if (options.json) {
1481
+ printJson(result);
1482
+ return;
1483
+ }
1484
+ consola.success(`Booked ${amount} on voucher ${voucherId}. Status changed from ${result.fromStatus} to ${result.toStatus}.`);
1485
+ }
1486
+ });
1487
+ //#endregion
1488
+ //#region src/commands/vouchers/create.ts
1489
+ var create_default = defineCommand({
1490
+ description: "Create a voucher from a receipt file.",
1491
+ options: defineOptions(z.object({
1492
+ file: z.string().optional().describe("Path to the receipt file to attach, for example a PDF, an image or an XML invoice."),
1493
+ status: z.enum(["draft", "open"]).default("open").describe("Status of the created voucher. Supported values are draft and open."),
1494
+ creditDebit: z.enum(["C", "D"]).default("C").describe("C for expense (credit), D for income (debit) vouchers."),
1495
+ taxRule: z.coerce.number().optional().describe("ID of the tax rule, for example 9 for deductible input tax expenses."),
1496
+ accountDatev: z.coerce.number().optional().describe("ID of the booking account (AccountDatev). Find it with guidance:accounts."),
1497
+ amount: z.coerce.number().optional().describe("Amount of the voucher position."),
1498
+ net: z.boolean().default(false).describe("Treat the amount as net instead of gross."),
1499
+ taxRate: z.coerce.number().default(19).describe("Tax rate of the voucher position in percent."),
1500
+ voucherDate: z.string().optional().describe("Date as dd.mm.yyyy or Unix timestamp."),
1501
+ payDate: z.string().optional().describe("Date as dd.mm.yyyy or Unix timestamp."),
1502
+ supplierId: z.coerce.number().optional().describe("ID of the supplier contact."),
1503
+ supplierName: z.string().optional().describe("Name of the supplier, used when no supplier ID is given."),
1504
+ description: z.string().optional().describe("The voucher number."),
1505
+ comment: z.string().optional().describe("Comment for the voucher position."),
1506
+ json: z.boolean().default(false).describe("Output in JSON format.")
1507
+ })),
1508
+ action: async (options) => {
1509
+ const client = await requireClient();
1510
+ const path = await requireStringOption({
1511
+ value: options.file,
1512
+ requirement: "a file via --file",
1513
+ question: "Enter the path to the receipt file:"
1514
+ });
1515
+ const taxRuleId = await requireNumberOption({
1516
+ value: options.taxRule,
1517
+ requirement: "a tax rule via --tax-rule",
1518
+ question: "Enter the tax rule ID:"
1519
+ });
1520
+ const accountDatevId = await requireNumberOption({
1521
+ value: options.accountDatev,
1522
+ requirement: "a booking account via --account-datev",
1523
+ question: "Enter the booking account ID:"
1524
+ });
1525
+ const amount = await requireNumberOption({
1526
+ value: options.amount,
1527
+ requirement: "an amount via --amount",
1528
+ question: "Enter the amount:"
1529
+ });
1530
+ const supplierName = options.supplierId === void 0 && !options.supplierName ? await requireStringOption({
1531
+ value: void 0,
1532
+ requirement: "a supplier via --supplier-id or --supplier-name",
1533
+ question: "Enter the supplier name:"
1534
+ }) : options.supplierName;
1535
+ const file = await readFile(path).catch(() => {
1536
+ consola.error(`The file "${path}" could not be read. Check that the path is correct.`);
1537
+ process.exit(1);
1538
+ });
1539
+ const result = await client.vouchers.createFromFile({
1540
+ file,
1541
+ filename: basename(path),
1542
+ voucher: {
1543
+ status: options.status === "draft" ? 50 : 100,
1544
+ creditDebit: options.creditDebit,
1545
+ taxRuleId,
1546
+ voucherDate: options.voucherDate,
1547
+ payDate: options.payDate,
1548
+ supplierId: options.supplierId,
1549
+ supplierName,
1550
+ description: options.description
1551
+ },
1552
+ positions: [{
1553
+ accountDatevId,
1554
+ taxRate: options.taxRate,
1555
+ net: options.net,
1556
+ ...options.net ? { sumNet: amount } : { sumGross: amount },
1557
+ comment: options.comment
1558
+ }]
1559
+ });
1560
+ if (options.json) {
1561
+ printJson(result);
1562
+ return;
1563
+ }
1564
+ consola.success(`Created voucher ${result.voucher.id} with status ${result.voucher.status}.`);
1565
+ }
1566
+ });
1567
+ //#endregion
1568
+ //#region src/commands/vouchers/enshrine.ts
1569
+ var enshrine_default = defineCommand({
1570
+ description: "Enshrine a voucher so that it can no longer be changed.",
1571
+ options: defineOptions(z.object({
1572
+ id: z.coerce.number().optional().describe("ID of the voucher."),
1573
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
1574
+ json: z.boolean().default(false).describe("Output in JSON format.")
1575
+ })),
1576
+ action: async (options) => {
1577
+ const client = await requireClient();
1578
+ const voucherId = await requireNumberOption({
1579
+ value: options.id,
1580
+ requirement: "a voucher ID via --id",
1581
+ question: "Enter the voucher ID:"
1582
+ });
1583
+ if (!await confirmOrAbort({
1584
+ message: `Enshrine voucher ${voucherId}? This cannot be undone.`,
1585
+ yes: options.yes,
1586
+ initial: false
1587
+ })) return;
1588
+ await client.vouchers.enshrine({ voucherId });
1589
+ if (options.json) {
1590
+ printJson({ enshrined: true });
1591
+ return;
1592
+ }
1593
+ consola.success(`Enshrined voucher ${voucherId}.`);
1594
+ }
1595
+ });
1596
+ //#endregion
1597
+ //#region src/commands/vouchers/get.ts
1598
+ var get_default = defineCommand({
1599
+ description: "Show a single voucher.",
1600
+ options: defineOptions(z.object({
1601
+ id: z.coerce.number().optional().describe("ID of the voucher."),
1602
+ json: z.boolean().default(false).describe("Output in JSON format.")
1603
+ })),
1604
+ action: async (options) => {
1605
+ const client = await requireClient();
1606
+ const voucherId = await requireNumberOption({
1607
+ value: options.id,
1608
+ requirement: "a voucher ID via --id",
1609
+ question: "Enter the voucher ID:"
1610
+ });
1611
+ const voucher = await client.vouchers.get({ voucherId });
1612
+ if (options.json) {
1613
+ printJson(voucher);
1614
+ return;
1615
+ }
1616
+ consola.info(`ID: ${voucher.id}`);
1617
+ consola.info(`Status: ${voucher.status ?? "-"}`);
1618
+ consola.info(`Date: ${voucher.voucherDate ?? "-"}`);
1619
+ consola.info(`Supplier: ${voucher.supplierName ?? voucher.supplier?.id ?? "-"}`);
1620
+ consola.info(`Description: ${voucher.description ?? "-"}`);
1621
+ consola.info(`Net: ${voucher.sumNet}`);
1622
+ consola.info(`Tax: ${voucher.sumTax}`);
1623
+ consola.info(`Gross: ${voucher.sumGross}`);
1624
+ consola.info(`Currency: ${voucher.currency ?? "-"}`);
1625
+ consola.info(`Enshrined: ${voucher.enshrined ?? "no"}`);
1626
+ }
1627
+ });
1628
+ //#endregion
1629
+ //#region src/commands/vouchers/list.ts
1630
+ var list_default = defineCommand({
1631
+ description: "List vouchers.",
1632
+ options: defineOptions(z.object({
1633
+ status: z.coerce.number().optional().describe("Filter by status: 50 draft, 100 open, 150 transferred, 750 partially paid, 1000 paid."),
1634
+ creditDebit: z.enum(["C", "D"]).optional().describe("Filter by C for expense or D for income vouchers."),
1635
+ descriptionLike: z.string().optional().describe("Filter by voucher number, matching partially."),
1636
+ startDate: z.string().optional().describe("Only vouchers on or after this date as dd.mm.yyyy or Unix timestamp."),
1637
+ endDate: z.string().optional().describe("Only vouchers on or before this date as dd.mm.yyyy or Unix timestamp."),
1638
+ contact: z.coerce.number().optional().describe("ID of the contact whose vouchers to list."),
1639
+ limit: z.coerce.number().optional().describe("Maximum number of vouchers to return."),
1640
+ offset: z.coerce.number().optional().describe("Number of vouchers to skip."),
1641
+ json: z.boolean().default(false).describe("Output in JSON format.")
1642
+ })),
1643
+ action: async (options) => {
1644
+ const vouchers = await (await requireClient()).vouchers.list({
1645
+ status: options.status,
1646
+ creditDebit: options.creditDebit,
1647
+ descriptionLike: options.descriptionLike,
1648
+ startDate: options.startDate,
1649
+ endDate: options.endDate,
1650
+ contactId: options.contact,
1651
+ limit: options.limit,
1652
+ offset: options.offset
1653
+ });
1654
+ if (options.json) {
1655
+ printJson(vouchers);
1656
+ return;
1657
+ }
1658
+ if (vouchers.length === 0) {
1659
+ consola.info("No vouchers found.");
1660
+ return;
1661
+ }
1662
+ printTable(vouchers.map((voucher) => ({
1663
+ id: voucher.id,
1664
+ date: voucher.voucherDate ?? "",
1665
+ supplier: voucher.supplierName ?? voucher.supplier?.id ?? "",
1666
+ description: voucher.description ?? "",
1667
+ gross: voucher.sumGross,
1668
+ status: voucher.status ?? ""
1669
+ })));
1670
+ }
1671
+ });
1672
+ //#endregion
1673
+ //#region src/commands/vouchers/reset-to-draft.ts
1674
+ var reset_to_draft_default = defineCommand({
1675
+ description: "Reset a voucher to the draft status.",
1676
+ options: defineOptions(z.object({
1677
+ id: z.coerce.number().optional().describe("ID of the voucher."),
1678
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
1679
+ json: z.boolean().default(false).describe("Output in JSON format.")
1680
+ })),
1681
+ action: async (options) => {
1682
+ const client = await requireClient();
1683
+ const voucherId = await requireNumberOption({
1684
+ value: options.id,
1685
+ requirement: "a voucher ID via --id",
1686
+ question: "Enter the voucher ID:"
1687
+ });
1688
+ if (!await confirmOrAbort({
1689
+ message: `Reset voucher ${voucherId} to draft? This unlinks existing payments.`,
1690
+ yes: options.yes,
1691
+ initial: false
1692
+ })) return;
1693
+ const voucher = await client.vouchers.resetToDraft({ voucherId });
1694
+ if (options.json) {
1695
+ printJson(voucher);
1696
+ return;
1697
+ }
1698
+ consola.success(`Reset voucher ${voucher.id}. New status: ${voucher.status ?? "-"}.`);
1699
+ }
1700
+ });
1701
+ //#endregion
1702
+ //#region src/commands/vouchers/reset-to-open.ts
1703
+ var reset_to_open_default = defineCommand({
1704
+ description: "Reset a voucher to the open status.",
1705
+ options: defineOptions(z.object({
1706
+ id: z.coerce.number().optional().describe("ID of the voucher."),
1707
+ yes: z.boolean().default(false).describe("Skip the confirmation prompt."),
1708
+ json: z.boolean().default(false).describe("Output in JSON format.")
1709
+ })),
1710
+ action: async (options) => {
1711
+ const client = await requireClient();
1712
+ const voucherId = await requireNumberOption({
1713
+ value: options.id,
1714
+ requirement: "a voucher ID via --id",
1715
+ question: "Enter the voucher ID:"
1716
+ });
1717
+ if (!await confirmOrAbort({
1718
+ message: `Reset voucher ${voucherId} to open? This unlinks existing payments.`,
1719
+ yes: options.yes,
1720
+ initial: false
1721
+ })) return;
1722
+ const voucher = await client.vouchers.resetToOpen({ voucherId });
1723
+ if (options.json) {
1724
+ printJson(voucher);
1725
+ return;
1726
+ }
1727
+ consola.success(`Reset voucher ${voucher.id}. New status: ${voucher.status ?? "-"}.`);
1728
+ }
1729
+ });
1730
+ //#endregion
1731
+ //#region src/index.ts
1732
+ const config = defineConfig({
1733
+ meta: {
1734
+ name: "sevdesk",
1735
+ version: pkg.version,
1736
+ description: pkg.description
1737
+ },
1738
+ commands: {
1739
+ login: login_default,
1740
+ logout: logout_default,
1741
+ doctor: doctor_default,
1742
+ "accounts:balance": balance_default,
1743
+ "accounts:create-clearing": create_clearing_default,
1744
+ "accounts:create-file-import": create_file_import_default,
1745
+ "accounts:get": get_default$7,
1746
+ "accounts:list": list_default$8,
1747
+ "contacts:create": create_default$3,
1748
+ "contacts:delete": delete_default$1,
1749
+ "contacts:get": get_default$6,
1750
+ "contacts:list": list_default$7,
1751
+ "contacts:update": update_default,
1752
+ "credit-notes:get": get_default$5,
1753
+ "credit-notes:list": list_default$6,
1754
+ "credit-notes:pdf": pdf_default$2,
1755
+ "export:contacts": contacts_default,
1756
+ "export:datev": datev_default,
1757
+ "export:invoices": invoices_default,
1758
+ "export:transactions": transactions_default,
1759
+ "export:vouchers": vouchers_default,
1760
+ "guidance:accounts": accounts_default,
1761
+ "invoices:get": get_default$4,
1762
+ "invoices:list": list_default$5,
1763
+ "invoices:pdf": pdf_default$1,
1764
+ "orders:get": get_default$3,
1765
+ "orders:list": list_default$4,
1766
+ "orders:pdf": pdf_default,
1767
+ "parts:get": get_default$2,
1768
+ "parts:list": list_default$3,
1769
+ "tags:create": create_default$2,
1770
+ "tags:delete": delete_default,
1771
+ "tags:list": list_default$2,
1772
+ "transactions:create": create_default$1,
1773
+ "transactions:get": get_default$1,
1774
+ "transactions:list": list_default$1,
1775
+ "vouchers:book": book_default,
1776
+ "vouchers:create": create_default,
1777
+ "vouchers:enshrine": enshrine_default,
1778
+ "vouchers:get": get_default,
1779
+ "vouchers:list": list_default,
1780
+ "vouchers:reset-to-draft": reset_to_draft_default,
1781
+ "vouchers:reset-to-open": reset_to_open_default
1782
+ }
1783
+ });
1784
+ try {
1785
+ const result = processConfig(config, process.argv.slice(2));
1786
+ await result.command.action(result.options, result.args);
1787
+ } catch (error) {
1788
+ consola.error(formatError(error));
1789
+ process.exitCode = 1;
1790
+ }
1791
+ //#endregion
1792
+ export {};
1793
+
1794
+ //# sourceMappingURL=index.js.map