Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ ${colors.bold('OPTIONS:')}
--no-color Disable colored output
--no-cache Bypass cache for this request
--refresh Force refresh cached data
--dry-run Show what would happen without making changes
-p, --page <num> Page number for pagination
-s, --size <num> Page size (default: 100)
--sort <field> Sort by field (prefix with - for descending)
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/commands/attachments/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
createRenderContext,
humanAttachmentDetailRenderer,
} from '../../renderers/index.js';
import { isDryRun, handleDryRunOutput } from '../../utils/dry-run.js';
import { parseFilters } from '../../utils/parse-filters.js';

function parseListOptions(ctx: CommandContext): ListAttachmentsOptions {
Expand Down Expand Up @@ -105,6 +106,20 @@ export async function attachmentsDelete(args: string[], ctx: CommandContext): Pr

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);

if (isDryRun(ctx)) {
handleDryRunOutput(
{
action: 'delete',
resource: 'attachment',
resourceId: id,
},
ctx,
spinner,
);
return;
}

await deleteAttachment({ id }, execCtx);

spinner.succeed();
Expand Down
104 changes: 68 additions & 36 deletions packages/cli/src/commands/bookings/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { handleError, exitWithValidationError, runCommand } from '../../error-ha
import { ValidationError } from '../../errors.js';
import { render, createRenderContext, humanBookingDetailRenderer } from '../../renderers/index.js';
import { colors } from '../../utils/colors.js';
import { isDryRun, handleDryRunOutput } from '../../utils/dry-run.js';
import { parseFilters } from '../../utils/parse-filters.js';

function formatDuration(minutes: number): string {
Expand Down Expand Up @@ -161,23 +162,43 @@ export async function bookingsAdd(ctx: CommandContext): Promise<void> {

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);
const result = await createBooking(
{
personId,
serviceId: ctx.options.service ? String(ctx.options.service) : undefined,
eventId: ctx.options.event ? String(ctx.options.event) : undefined,
startedOn: String(ctx.options.from),
endedOn: String(ctx.options.to),
time: ctx.options.time ? parseInt(String(ctx.options.time)) : undefined,
totalTime: ctx.options['total-time']
? parseInt(String(ctx.options['total-time']))
: undefined,
percentage: ctx.options.percentage ? parseInt(String(ctx.options.percentage)) : undefined,
draft: ctx.options.tentative === true,
note: ctx.options.note ? String(ctx.options.note) : undefined,
},
execCtx,
);
const payload = {
personId,
serviceId: ctx.options.service ? String(ctx.options.service) : undefined,
eventId: ctx.options.event ? String(ctx.options.event) : undefined,
startedOn: String(ctx.options.from),
endedOn: String(ctx.options.to),
time: ctx.options.time ? parseInt(String(ctx.options.time)) : undefined,
totalTime: ctx.options['total-time']
? parseInt(String(ctx.options['total-time']))
: undefined,
percentage: ctx.options.percentage ? parseInt(String(ctx.options.percentage)) : undefined,
draft: ctx.options.tentative === true,
note: ctx.options.note ? String(ctx.options.note) : undefined,
};

if (isDryRun(ctx)) {
const target = ctx.options.service
? `service ${payload.serviceId}`
: ctx.options.event
? `event ${payload.eventId}`
: 'unknown';
const period = `${payload.startedOn} → ${payload.endedOn}`;

handleDryRunOutput(
{
action: 'create',
resource: 'booking',
payload,
description: `Booking for ${target} (${period})`,
},
ctx,
spinner,
);
return;
}

const result = await createBooking(payload, execCtx);

spinner.succeed();

Expand Down Expand Up @@ -214,25 +235,36 @@ export async function bookingsUpdate(args: string[], ctx: CommandContext): Promi
if (ctx.options.confirm !== undefined) draft = false;
else if (ctx.options.tentative !== undefined) draft = ctx.options.tentative === true;

const result = await updateBooking(
{
id,
startedOn: ctx.options.from !== undefined ? String(ctx.options.from) : undefined,
endedOn: ctx.options.to !== undefined ? String(ctx.options.to) : undefined,
time: ctx.options.time !== undefined ? parseInt(String(ctx.options.time)) : undefined,
totalTime:
ctx.options['total-time'] !== undefined
? parseInt(String(ctx.options['total-time']))
: undefined,
percentage:
ctx.options.percentage !== undefined
? parseInt(String(ctx.options.percentage))
: undefined,
draft,
note: ctx.options.note !== undefined ? String(ctx.options.note) : undefined,
},
execCtx,
);
const payload = {
id,
startedOn: ctx.options.from !== undefined ? String(ctx.options.from) : undefined,
endedOn: ctx.options.to !== undefined ? String(ctx.options.to) : undefined,
time: ctx.options.time !== undefined ? parseInt(String(ctx.options.time)) : undefined,
totalTime:
ctx.options['total-time'] !== undefined
? parseInt(String(ctx.options['total-time']))
: undefined,
percentage:
ctx.options.percentage !== undefined ? parseInt(String(ctx.options.percentage)) : undefined,
draft,
note: ctx.options.note !== undefined ? String(ctx.options.note) : undefined,
};

if (isDryRun(ctx)) {
handleDryRunOutput(
{
action: 'update',
resource: 'booking',
resourceId: id,
payload,
},
ctx,
spinner,
);
return;
}

const result = await updateBooking(payload, execCtx);

spinner.succeed();

Expand Down
90 changes: 69 additions & 21 deletions packages/cli/src/commands/comments/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { handleError, exitWithValidationError, runCommand } from '../../error-ha
import { ValidationError } from '../../errors.js';
import { render, createRenderContext, humanCommentDetailRenderer } from '../../renderers/index.js';
import { colors } from '../../utils/colors.js';
import { isDryRun, handleDryRunOutput } from '../../utils/dry-run.js';
import { parseFilters } from '../../utils/parse-filters.js';

function parseListOptions(ctx: CommandContext): ListCommentsOptions {
Expand Down Expand Up @@ -153,19 +154,53 @@ export async function commentsAdd(ctx: CommandContext): Promise<void> {

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);
const result = await createComment(
{
body: String(ctx.options.body),
hidden: ctx.options.hidden === true ? true : undefined,
taskId: ctx.options.task ? String(ctx.options.task) : undefined,
dealId: ctx.options.deal ? String(ctx.options.deal) : undefined,
companyId: ctx.options.company ? String(ctx.options.company) : undefined,
invoiceId: ctx.options.invoice ? String(ctx.options.invoice) : undefined,
personId: ctx.options.person ? String(ctx.options.person) : undefined,
discussionId: ctx.options.discussion ? String(ctx.options.discussion) : undefined,
},
execCtx,
);
const payload = {
body: String(ctx.options.body),
hidden: ctx.options.hidden === true ? true : undefined,
taskId: ctx.options.task ? String(ctx.options.task) : undefined,
dealId: ctx.options.deal ? String(ctx.options.deal) : undefined,
companyId: ctx.options.company ? String(ctx.options.company) : undefined,
invoiceId: ctx.options.invoice ? String(ctx.options.invoice) : undefined,
personId: ctx.options.person ? String(ctx.options.person) : undefined,
discussionId: ctx.options.discussion ? String(ctx.options.discussion) : undefined,
};

if (isDryRun(ctx)) {
const parentType = ctx.options.task
? 'task'
: ctx.options.deal
? 'deal'
: ctx.options.company
? 'company'
: ctx.options.invoice
? 'invoice'
: ctx.options.person
? 'person'
: ctx.options.discussion
? 'discussion'
: 'unknown';
const parentId =
ctx.options.task ||
ctx.options.deal ||
ctx.options.company ||
ctx.options.invoice ||
ctx.options.person ||
ctx.options.discussion;

handleDryRunOutput(
{
action: 'create',
resource: 'comment',
payload,
description: `Comment on ${parentType} ${parentId}`,
},
ctx,
spinner,
);
return;
}

const result = await createComment(payload, execCtx);

spinner.succeed();

Expand Down Expand Up @@ -213,14 +248,27 @@ export async function commentsUpdate(args: string[], ctx: CommandContext): Promi

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);
const result = await updateComment(
{
id,
body: hasBody ? String(ctx.options.body) : undefined,
hidden,
},
execCtx,
);
const payload = {
id,
body: hasBody ? String(ctx.options.body) : undefined,
hidden,
};

if (isDryRun(ctx)) {
handleDryRunOutput(
{
action: 'update',
resource: 'comment',
resourceId: id,
payload,
},
ctx,
spinner,
);
return;
}

const result = await updateComment(payload, execCtx);

spinner.succeed();

Expand Down
97 changes: 67 additions & 30 deletions packages/cli/src/commands/companies/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { handleError, exitWithValidationError, runCommand } from '../../error-ha
import { ValidationError } from '../../errors.js';
import { render, createRenderContext, humanCompanyDetailRenderer } from '../../renderers/index.js';
import { colors } from '../../utils/colors.js';
import { isDryRun, handleDryRunOutput } from '../../utils/dry-run.js';
import { parseFilters } from '../../utils/parse-filters.js';

export async function companiesList(ctx: CommandContext): Promise<void> {
Expand Down Expand Up @@ -103,18 +104,31 @@ export async function companiesAdd(ctx: CommandContext): Promise<void> {

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);
const result = await createCompany(
{
name: String(ctx.options.name),
billingName: ctx.options['billing-name'] ? String(ctx.options['billing-name']) : undefined,
vat: ctx.options.vat ? String(ctx.options.vat) : undefined,
defaultCurrency: ctx.options.currency ? String(ctx.options.currency) : undefined,
companyCode: ctx.options.code ? String(ctx.options.code) : undefined,
domain: ctx.options.domain ? String(ctx.options.domain) : undefined,
dueDays: ctx.options['due-days'] ? parseInt(String(ctx.options['due-days'])) : undefined,
},
execCtx,
);
const payload = {
name: String(ctx.options.name),
billingName: ctx.options['billing-name'] ? String(ctx.options['billing-name']) : undefined,
vat: ctx.options.vat ? String(ctx.options.vat) : undefined,
defaultCurrency: ctx.options.currency ? String(ctx.options.currency) : undefined,
companyCode: ctx.options.code ? String(ctx.options.code) : undefined,
domain: ctx.options.domain ? String(ctx.options.domain) : undefined,
dueDays: ctx.options['due-days'] ? parseInt(String(ctx.options['due-days'])) : undefined,
};

if (isDryRun(ctx)) {
handleDryRunOutput(
{
action: 'create',
resource: 'company',
payload,
description: `Company "${payload.name}"`,
},
ctx,
spinner,
);
return;
}

const result = await createCompany(payload, execCtx);

spinner.succeed();

Expand Down Expand Up @@ -144,28 +158,51 @@ export async function companiesUpdate(args: string[], ctx: CommandContext): Prom

await runCommand(async () => {
const execCtx = fromCommandContext(ctx);
const payload = {
id,
name: ctx.options.name !== undefined ? String(ctx.options.name) : undefined,
billingName:
ctx.options['billing-name'] !== undefined ? String(ctx.options['billing-name']) : undefined,
vat: ctx.options.vat !== undefined ? String(ctx.options.vat) : undefined,
defaultCurrency:
ctx.options.currency !== undefined ? String(ctx.options.currency) : undefined,
companyCode: ctx.options.code !== undefined ? String(ctx.options.code) : undefined,
domain: ctx.options.domain !== undefined ? String(ctx.options.domain) : undefined,
dueDays:
ctx.options['due-days'] !== undefined
? parseInt(String(ctx.options['due-days']))
: undefined,
};

// Check for validation before dry-run to ensure consistent behavior
const hasUpdates = Object.entries(payload).some(
([key, value]) => key !== 'id' && value !== undefined,
);
if (!hasUpdates) {
spinner.fail();
throw ValidationError.invalid(
'options',
{},
'No updates specified. Use --name, --billing-name, --vat, --currency, etc.',
);
}

try {
const result = await updateCompany(
if (isDryRun(ctx)) {
handleDryRunOutput(
{
id,
name: ctx.options.name !== undefined ? String(ctx.options.name) : undefined,
billingName:
ctx.options['billing-name'] !== undefined
? String(ctx.options['billing-name'])
: undefined,
vat: ctx.options.vat !== undefined ? String(ctx.options.vat) : undefined,
defaultCurrency:
ctx.options.currency !== undefined ? String(ctx.options.currency) : undefined,
companyCode: ctx.options.code !== undefined ? String(ctx.options.code) : undefined,
domain: ctx.options.domain !== undefined ? String(ctx.options.domain) : undefined,
dueDays:
ctx.options['due-days'] !== undefined
? parseInt(String(ctx.options['due-days']))
: undefined,
action: 'update',
resource: 'company',
resourceId: id,
payload,
},
execCtx,
ctx,
spinner,
);
return;
}

try {
const result = await updateCompany(payload, execCtx);

spinner.succeed();

Expand Down
Loading
Loading