Midday / apps /api /src /ai /tools /get-invoices.ts
Jules
Final deployment with all fixes and verified content
c09f67c
import type { AppContext } from "@api/ai/agents/config/shared";
import { db } from "@midday/db/client";
import { getInvoices } from "@midday/db/queries";
import { getAppUrl } from "@midday/utils/envs";
import { formatAmount, formatDate } from "@midday/utils/format";
import { tool } from "ai";
import { z } from "zod";
const getInvoicesSchema = z.object({
cursor: z.string().nullable().optional().describe("Pagination cursor"),
sort: z
.array(z.string())
.length(2)
.nullable()
.optional()
.describe("Sort order"),
pageSize: z.number().min(1).max(100).default(10).describe("Page size"),
q: z.string().nullable().optional().describe("Search query"),
start: z.string().nullable().optional().describe("Start date (ISO 8601)"),
end: z.string().nullable().optional().describe("End date (ISO 8601)"),
statuses: z
.array(
z.enum(["draft", "overdue", "paid", "unpaid", "canceled", "scheduled"]),
)
.nullable()
.optional()
.describe("Status filter"),
customers: z.array(z.string()).nullable().optional().describe("Customer IDs"),
});
export const getInvoicesTool = tool({
description:
"Retrieve and filter invoices with pagination, sorting, and search.",
inputSchema: getInvoicesSchema,
execute: async function* (
{ cursor, sort, pageSize = 10, q, start, end, statuses, customers },
executionOptions,
) {
const appContext = executionOptions.experimental_context as AppContext;
const teamId = appContext.teamId as string;
if (!teamId) {
yield {
text: "Unable to retrieve invoices: Team ID not found in context.",
};
return;
}
try {
const params = {
teamId,
cursor: cursor ?? null,
sort: sort ?? null,
pageSize,
q: q ?? null,
start: start ?? null,
end: end ?? null,
statuses: statuses ?? null,
customers: customers ?? null,
};
const result = await getInvoices(db, params);
if (result.data.length === 0) {
yield { text: "No invoices found matching your criteria." };
return;
}
const locale = appContext.locale ?? "en-US";
const baseCurrency = appContext.baseCurrency ?? "USD";
const formattedInvoices = result.data.map((invoice) => {
const formattedAmount = formatAmount({
amount: invoice.amount ?? 0,
currency: invoice.currency || baseCurrency,
locale,
});
return {
id: invoice.id,
invoiceNumber: invoice.invoiceNumber || "Draft",
customerName:
invoice.customerName || invoice.customer?.name || "No customer",
amount: formattedAmount,
status: invoice.status,
dueDate: invoice.dueDate ? formatDate(invoice.dueDate) : "N/A",
createdAt: formatDate(invoice.createdAt),
};
});
const totalAmount = result.data.reduce(
(sum, inv) => sum + (inv.amount ?? 0),
0,
);
const formattedTotalAmount = formatAmount({
amount: totalAmount,
currency: baseCurrency,
locale,
});
const paidCount = result.data.filter(
(inv) => inv.status === "paid",
).length;
const unpaidCount = result.data.filter(
(inv) => inv.status === "unpaid",
).length;
const overdueCount = result.data.filter(
(inv) => inv.status === "overdue",
).length;
const response = `| Invoice # | Customer | Amount | Status | Due Date | Created |\n|-----------|---------|--------|--------|----------|----------|\n${formattedInvoices.map((inv) => `| ${inv.invoiceNumber} | ${inv.customerName} | ${inv.amount} | ${inv.status} | ${inv.dueDate} | ${inv.createdAt} |`).join("\n")}\n\n**${result.data.length} invoices** | Total: ${formattedTotalAmount} | Paid: ${paidCount} | Unpaid: ${unpaidCount} | Overdue: ${overdueCount}`;
yield {
text: response,
link: {
text: "View all invoices",
url: `${getAppUrl()}/invoices`,
},
};
} catch (error) {
yield {
text: `Failed to retrieve invoices: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
},
});