| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109 |
- /**
- * Database store for the dashboard API using Drizzle ORM
- */
- import { eq, ilike, or, desc, asc, max } from "drizzle-orm";
- import { db } from "./connection";
- import {
- customers,
- invoices,
- expenses,
- accounts,
- transactions,
- widgets,
- type Customer,
- type Invoice,
- type Expense,
- type InvoiceItem,
- type WidgetSpec,
- } from "./schema";
- // Helper to generate IDs
- const generateId = (prefix: string) => `${prefix}-${Date.now()}`;
- // Customer methods
- export async function getCustomers(filters?: {
- status?: string;
- search?: string;
- limit?: number;
- sort?: "newest" | "oldest";
- }) {
- let query = db.select().from(customers);
- if (filters?.status) {
- query = query.where(
- eq(customers.status, filters.status as "active" | "inactive"),
- ) as typeof query;
- }
- if (filters?.search) {
- const searchPattern = `%${filters.search}%`;
- query = query.where(
- or(
- ilike(customers.name, searchPattern),
- ilike(customers.email, searchPattern),
- ),
- ) as typeof query;
- }
- // Default to newest first
- const sortOrder = filters?.sort === "oldest" ? asc : desc;
- query = query.orderBy(sortOrder(customers.createdAt)) as typeof query;
- if (filters?.limit) {
- query = query.limit(filters.limit) as typeof query;
- }
- return query;
- }
- export async function getCustomer(id: string) {
- const result = await db
- .select()
- .from(customers)
- .where(eq(customers.id, id))
- .limit(1);
- return result[0] || null;
- }
- export async function createCustomer(data: {
- name: string;
- email: string;
- phone?: string;
- }) {
- const id = generateId("cust");
- const [customer] = await db
- .insert(customers)
- .values({
- id,
- name: data.name,
- email: data.email,
- phone: data.phone || null,
- balance: "0",
- status: "active",
- })
- .returning();
- return customer;
- }
- export async function updateCustomer(id: string, data: Partial<Customer>) {
- const [customer] = await db
- .update(customers)
- .set(data)
- .where(eq(customers.id, id))
- .returning();
- return customer || null;
- }
- export async function deleteCustomer(id: string) {
- const result = await db
- .delete(customers)
- .where(eq(customers.id, id))
- .returning({ id: customers.id });
- return { success: result.length > 0 };
- }
- // Invoice methods
- export async function getInvoices(filters?: {
- status?: string;
- customerId?: string;
- }) {
- let query = db.select().from(invoices);
- if (filters?.status) {
- query = query.where(
- eq(invoices.status, filters.status as Invoice["status"]),
- ) as typeof query;
- }
- if (filters?.customerId) {
- query = query.where(
- eq(invoices.customerId, filters.customerId),
- ) as typeof query;
- }
- return query;
- }
- export async function getInvoice(id: string) {
- const result = await db
- .select()
- .from(invoices)
- .where(eq(invoices.id, id))
- .limit(1);
- return result[0] || null;
- }
- export async function createInvoice(data: {
- customerId: string;
- dueDate: string;
- items?: InvoiceItem[];
- }) {
- const customer = await getCustomer(data.customerId);
- if (!customer) return null;
- const items = data.items || [];
- const amount = items.reduce((sum, item) => sum + item.amount, 0);
- const id = generateId("inv");
- const [invoice] = await db
- .insert(invoices)
- .values({
- id,
- customerId: data.customerId,
- customerName: customer.name,
- amount: amount.toString(),
- status: "draft",
- dueDate: new Date(data.dueDate),
- items,
- })
- .returning();
- return invoice;
- }
- export async function updateInvoice(id: string, data: Partial<Invoice>) {
- const [invoice] = await db
- .update(invoices)
- .set(data)
- .where(eq(invoices.id, id))
- .returning();
- return invoice || null;
- }
- export async function deleteInvoice(id: string) {
- const result = await db
- .delete(invoices)
- .where(eq(invoices.id, id))
- .returning({ id: invoices.id });
- return result.length > 0;
- }
- export async function sendInvoice(id: string) {
- const invoice = await getInvoice(id);
- if (!invoice) return { error: "Invoice not found" };
- if (invoice.status !== "draft")
- return { error: "Only draft invoices can be sent" };
- return updateInvoice(id, { status: "sent" });
- }
- export async function markInvoicePaid(id: string) {
- const invoice = await getInvoice(id);
- if (!invoice) return { error: "Invoice not found" };
- if (invoice.status === "paid") return { error: "Invoice is already paid" };
- // Update customer balance
- const customer = await getCustomer(invoice.customerId);
- if (customer) {
- const newBalance =
- parseFloat(customer.balance as string) -
- parseFloat(invoice.amount as string);
- await updateCustomer(invoice.customerId, {
- balance: newBalance.toString(),
- });
- }
- // Add transaction
- await db.insert(transactions).values({
- id: generateId("txn"),
- accountId: "acc-001",
- type: "income",
- amount: invoice.amount,
- description: `Invoice #${invoice.id} payment from ${invoice.customerName}`,
- category: "Sales",
- date: new Date(),
- });
- return updateInvoice(id, { status: "paid" });
- }
- // Expense methods
- export async function getExpenses(filters?: {
- status?: string;
- category?: string;
- }) {
- let query = db.select().from(expenses);
- if (filters?.status) {
- query = query.where(
- eq(expenses.status, filters.status as Expense["status"]),
- ) as typeof query;
- }
- if (filters?.category) {
- query = query.where(
- eq(expenses.category, filters.category),
- ) as typeof query;
- }
- return query;
- }
- export async function getExpense(id: string) {
- const result = await db
- .select()
- .from(expenses)
- .where(eq(expenses.id, id))
- .limit(1);
- return result[0] || null;
- }
- export async function createExpense(data: {
- vendor: string;
- category: string;
- amount: number;
- date?: string;
- description?: string;
- }) {
- const id = generateId("exp");
- const [expense] = await db
- .insert(expenses)
- .values({
- id,
- vendor: data.vendor,
- category: data.category,
- amount: data.amount.toString(),
- date: data.date ? new Date(data.date) : new Date(),
- status: "pending",
- description: data.description || null,
- })
- .returning();
- return expense;
- }
- export async function updateExpense(id: string, data: Partial<Expense>) {
- const [expense] = await db
- .update(expenses)
- .set(data)
- .where(eq(expenses.id, id))
- .returning();
- return expense || null;
- }
- export async function deleteExpense(id: string) {
- const result = await db
- .delete(expenses)
- .where(eq(expenses.id, id))
- .returning({ id: expenses.id });
- return result.length > 0;
- }
- export async function approveExpense(id: string) {
- const expense = await getExpense(id);
- if (!expense) return { error: "Expense not found" };
- if (expense.status !== "pending")
- return { error: "Only pending expenses can be approved" };
- // Add transaction
- await db.insert(transactions).values({
- id: generateId("txn"),
- accountId: "acc-001",
- type: "expense",
- amount: expense.amount,
- description: `${expense.vendor} - ${expense.description || ""}`,
- category: expense.category,
- date: expense.date,
- });
- return updateExpense(id, { status: "approved" });
- }
- export async function rejectExpense(id: string) {
- const expense = await getExpense(id);
- if (!expense) return { error: "Expense not found" };
- if (expense.status !== "pending")
- return { error: "Only pending expenses can be rejected" };
- return updateExpense(id, { status: "rejected" });
- }
- // Account methods
- export async function getAccounts() {
- const accountList = await db.select().from(accounts);
- // Get recent transactions for each account
- const result = await Promise.all(
- accountList.map(async (account) => {
- const recentTxns = await db
- .select()
- .from(transactions)
- .where(eq(transactions.accountId, account.id))
- .orderBy(desc(transactions.date))
- .limit(5);
- return { ...account, recentTransactions: recentTxns };
- }),
- );
- return result;
- }
- export async function getAccount(id: string) {
- const result = await db
- .select()
- .from(accounts)
- .where(eq(accounts.id, id))
- .limit(1);
- if (!result[0]) return null;
- const txns = await db
- .select()
- .from(transactions)
- .where(eq(transactions.accountId, id))
- .orderBy(desc(transactions.date));
- return { ...result[0], transactions: txns };
- }
- // Dashboard summary
- export async function getDashboardSummary() {
- // Get all invoices
- const allInvoices = await db.select().from(invoices);
- const paidInvoices = allInvoices.filter((i) => i.status === "paid");
- const totalRevenue = paidInvoices.reduce(
- (sum, i) => sum + parseFloat(i.amount as string),
- 0,
- );
- const outstandingInvoices = allInvoices
- .filter((i) => i.status === "sent" || i.status === "overdue")
- .reduce((sum, i) => sum + parseFloat(i.amount as string), 0);
- const overdueAmount = allInvoices
- .filter((i) => i.status === "overdue")
- .reduce((sum, i) => sum + parseFloat(i.amount as string), 0);
- // Get all expenses
- const allExpenses = await db.select().from(expenses);
- const approvedExpenses = allExpenses.filter((e) => e.status === "approved");
- const totalExpenses = approvedExpenses.reduce(
- (sum, e) => sum + parseFloat(e.amount as string),
- 0,
- );
- const cashFlow = totalRevenue - totalExpenses;
- // Get accounts
- const allAccounts = await db.select().from(accounts);
- const totalBankBalance = allAccounts
- .filter((a) => a.type === "bank")
- .reduce((sum, a) => sum + parseFloat(a.balance as string), 0);
- // Get customers
- const allCustomers = await db.select().from(customers);
- // Get recent transactions
- const recentTxns = await db
- .select()
- .from(transactions)
- .orderBy(desc(transactions.date))
- .limit(5);
- // Group expenses by category
- const expensesByCategory = approvedExpenses.reduce(
- (acc, e) => {
- acc[e.category] = (acc[e.category] || 0) + parseFloat(e.amount as string);
- return acc;
- },
- {} as Record<string, number>,
- );
- return {
- revenue: {
- total: totalRevenue,
- outstanding: outstandingInvoices,
- overdue: overdueAmount,
- },
- expenses: {
- total: totalExpenses,
- pending: allExpenses.filter((e) => e.status === "pending").length,
- },
- cashFlow,
- bankBalance: totalBankBalance,
- customers: {
- total: allCustomers.length,
- active: allCustomers.filter((c) => c.status === "active").length,
- },
- invoices: {
- total: allInvoices.length,
- draft: allInvoices.filter((i) => i.status === "draft").length,
- sent: allInvoices.filter((i) => i.status === "sent").length,
- paid: allInvoices.filter((i) => i.status === "paid").length,
- overdue: allInvoices.filter((i) => i.status === "overdue").length,
- },
- recentTransactions: recentTxns.map((t) => ({
- ...t,
- amount: parseFloat(t.amount as string),
- })),
- expensesByCategory: Object.entries(expensesByCategory).map(
- ([label, value]) => ({
- label,
- value,
- }),
- ),
- revenueByMonth: [
- { label: "Oct", value: 18000 },
- { label: "Nov", value: 22000 },
- { label: "Dec", value: 28000 },
- { label: "Jan", value: 35000 },
- { label: "Feb", value: totalRevenue },
- ],
- };
- }
- // Reports
- export async function getProfitLossReport(
- startDate?: string,
- endDate?: string,
- ) {
- const start = startDate ? new Date(startDate) : new Date("2024-01-01");
- const end = endDate ? new Date(endDate) : new Date("2024-12-31");
- const allInvoices = await db.select().from(invoices);
- const paidInvoices = allInvoices.filter(
- (i) => i.status === "paid" && i.createdAt >= start && i.createdAt <= end,
- );
- const allExpenses = await db.select().from(expenses);
- const approvedExpenses = allExpenses.filter(
- (e) => e.status === "approved" && e.date >= start && e.date <= end,
- );
- const totalIncome = paidInvoices.reduce(
- (sum, i) => sum + parseFloat(i.amount as string),
- 0,
- );
- const totalExpensesAmount = approvedExpenses.reduce(
- (sum, e) => sum + parseFloat(e.amount as string),
- 0,
- );
- const netProfit = totalIncome - totalExpensesAmount;
- const expensesByCategory = approvedExpenses.reduce(
- (acc, e) => {
- acc[e.category] = (acc[e.category] || 0) + parseFloat(e.amount as string);
- return acc;
- },
- {} as Record<string, number>,
- );
- return {
- period: {
- startDate: start.toISOString().split("T")[0],
- endDate: end.toISOString().split("T")[0],
- },
- income: {
- total: totalIncome,
- invoiceCount: paidInvoices.length,
- },
- expenses: {
- total: totalExpensesAmount,
- expenseCount: approvedExpenses.length,
- byCategory: Object.entries(expensesByCategory).map(
- ([category, amount]) => ({
- category,
- amount,
- }),
- ),
- },
- netProfit,
- profitMargin: totalIncome > 0 ? (netProfit / totalIncome) * 100 : 0,
- };
- }
- // Clear database (delete all data)
- export async function clearDatabase() {
- await db.delete(transactions);
- await db.delete(invoices);
- await db.delete(expenses);
- await db.delete(customers);
- await db.delete(accounts);
- return { message: "Database cleared successfully" };
- }
- // Reset/seed database
- export async function resetDatabase() {
- // Delete all data
- await clearDatabase();
- // Seed accounts first
- await db.insert(accounts).values([
- {
- id: "acc-001",
- name: "Business Checking",
- type: "bank",
- balance: "125000",
- lastSync: new Date(),
- },
- {
- id: "acc-002",
- name: "Business Savings",
- type: "bank",
- balance: "75000",
- lastSync: new Date(),
- },
- {
- id: "acc-003",
- name: "Corporate Card",
- type: "credit_card",
- balance: "-8500",
- lastSync: new Date(),
- },
- {
- id: "acc-004",
- name: "Petty Cash",
- type: "cash",
- balance: "500",
- lastSync: new Date(),
- },
- ]);
- // Seed customers with dates spread over past 30 days
- const now = new Date();
- const daysAgo = (days: number) =>
- new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
- const seedCustomers = [
- // Enterprise customers (older)
- {
- id: "cust-001",
- name: "Acme Corporation",
- email: "billing@acme.com",
- phone: "(555) 123-4567",
- balance: "15000",
- status: "active" as const,
- createdAt: daysAgo(28),
- },
- {
- id: "cust-002",
- name: "Globex Industries",
- email: "ap@globex.com",
- phone: "(555) 234-5678",
- balance: "8500",
- status: "active" as const,
- createdAt: daysAgo(27),
- },
- {
- id: "cust-003",
- name: "Initech LLC",
- email: "finance@initech.com",
- phone: "(555) 345-6789",
- balance: "2300",
- status: "active" as const,
- createdAt: daysAgo(25),
- },
- {
- id: "cust-004",
- name: "Umbrella Corp",
- email: "accounts@umbrella.com",
- phone: "(555) 456-7890",
- balance: "45000",
- status: "active" as const,
- createdAt: daysAgo(24),
- },
- {
- id: "cust-005",
- name: "Stark Industries",
- email: "billing@stark.com",
- phone: "(555) 567-8901",
- balance: "0",
- status: "inactive" as const,
- createdAt: daysAgo(23),
- },
- // Week 3 signups
- {
- id: "cust-006",
- name: "Wayne Enterprises",
- email: "finance@wayne.com",
- phone: "(555) 678-9012",
- balance: "32000",
- status: "active" as const,
- createdAt: daysAgo(21),
- },
- {
- id: "cust-007",
- name: "Oscorp",
- email: "billing@oscorp.com",
- phone: "(555) 789-0123",
- balance: "12500",
- status: "active" as const,
- createdAt: daysAgo(20),
- },
- {
- id: "cust-008",
- name: "LexCorp",
- email: "accounts@lexcorp.com",
- phone: "(555) 890-1234",
- balance: "67000",
- status: "active" as const,
- createdAt: daysAgo(19),
- },
- {
- id: "cust-009",
- name: "Cyberdyne Systems",
- email: "ap@cyberdyne.com",
- phone: "(555) 901-2345",
- balance: "4500",
- status: "active" as const,
- createdAt: daysAgo(18),
- },
- {
- id: "cust-010",
- name: "Weyland-Yutani",
- email: "billing@weyland.com",
- phone: "(555) 012-3456",
- balance: "89000",
- status: "active" as const,
- createdAt: daysAgo(17),
- },
- // Week 2 signups (more activity)
- {
- id: "cust-011",
- name: "Tyrell Corp",
- email: "finance@tyrell.com",
- phone: "(555) 111-2222",
- balance: "23000",
- status: "active" as const,
- createdAt: daysAgo(14),
- },
- {
- id: "cust-012",
- name: "Soylent Corp",
- email: "billing@soylent.com",
- phone: "(555) 222-3333",
- balance: "5600",
- status: "active" as const,
- createdAt: daysAgo(14),
- },
- {
- id: "cust-013",
- name: "Massive Dynamic",
- email: "accounts@massive.com",
- phone: "(555) 333-4444",
- balance: "41000",
- status: "active" as const,
- createdAt: daysAgo(13),
- },
- {
- id: "cust-014",
- name: "Aperture Science",
- email: "billing@aperture.com",
- phone: "(555) 444-5555",
- balance: "0",
- status: "inactive" as const,
- createdAt: daysAgo(12),
- },
- {
- id: "cust-015",
- name: "Black Mesa",
- email: "finance@blackmesa.com",
- phone: "(555) 555-6666",
- balance: "18500",
- status: "active" as const,
- createdAt: daysAgo(11),
- },
- {
- id: "cust-016",
- name: "Vault-Tec",
- email: "sales@vaulttec.com",
- phone: "(555) 666-7777",
- balance: "72000",
- status: "active" as const,
- createdAt: daysAgo(10),
- },
- {
- id: "cust-017",
- name: "Abstergo Industries",
- email: "billing@abstergo.com",
- phone: "(555) 777-8888",
- balance: "9800",
- status: "active" as const,
- createdAt: daysAgo(10),
- },
- {
- id: "cust-018",
- name: "Shinra Electric",
- email: "accounts@shinra.com",
- phone: "(555) 888-9999",
- balance: "125000",
- status: "active" as const,
- createdAt: daysAgo(9),
- },
- // Week 1 signups (high activity)
- {
- id: "cust-019",
- name: "Monsters Inc",
- email: "billing@monsters.com",
- phone: "(555) 999-0000",
- balance: "34500",
- status: "active" as const,
- createdAt: daysAgo(7),
- },
- {
- id: "cust-020",
- name: "Buy n Large",
- email: "finance@bnl.com",
- phone: "(555) 000-1111",
- balance: "56000",
- status: "active" as const,
- createdAt: daysAgo(7),
- },
- {
- id: "cust-021",
- name: "Omni Consumer",
- email: "ap@omni.com",
- phone: "(555) 121-2121",
- balance: "8900",
- status: "active" as const,
- createdAt: daysAgo(6),
- },
- {
- id: "cust-022",
- name: "Rekall Inc",
- email: "billing@rekall.com",
- phone: "(555) 131-3131",
- balance: "15600",
- status: "active" as const,
- createdAt: daysAgo(5),
- },
- {
- id: "cust-023",
- name: "Virtucon",
- email: "accounts@virtucon.com",
- phone: "(555) 141-4141",
- balance: "28000",
- status: "active" as const,
- createdAt: daysAgo(5),
- },
- {
- id: "cust-024",
- name: "Wonka Industries",
- email: "finance@wonka.com",
- phone: "(555) 151-5151",
- balance: "47000",
- status: "active" as const,
- createdAt: daysAgo(4),
- },
- {
- id: "cust-025",
- name: "Dunder Mifflin",
- email: "sales@dundermifflin.com",
- phone: "(555) 161-6161",
- balance: "3200",
- status: "active" as const,
- createdAt: daysAgo(4),
- },
- {
- id: "cust-026",
- name: "Sterling Cooper",
- email: "billing@sterlingcooper.com",
- phone: "(555) 171-7171",
- balance: "19500",
- status: "active" as const,
- createdAt: daysAgo(3),
- },
- // Recent signups (last few days)
- {
- id: "cust-027",
- name: "Prestige Worldwide",
- email: "boats@prestige.com",
- phone: "(555) 181-8181",
- balance: "0",
- status: "active" as const,
- createdAt: daysAgo(2),
- },
- {
- id: "cust-028",
- name: "Pied Piper",
- email: "richard@piedpiper.com",
- phone: "(555) 191-9191",
- balance: "500",
- status: "active" as const,
- createdAt: daysAgo(2),
- },
- {
- id: "cust-029",
- name: "Hooli",
- email: "billing@hooli.com",
- phone: "(555) 202-0202",
- balance: "250000",
- status: "active" as const,
- createdAt: daysAgo(1),
- },
- {
- id: "cust-030",
- name: "Aviato",
- email: "erlich@aviato.com",
- phone: "(555) 212-1212",
- balance: "1200",
- status: "active" as const,
- createdAt: daysAgo(1),
- },
- {
- id: "cust-031",
- name: "Bluth Company",
- email: "gob@bluth.com",
- phone: "(555) 222-2222",
- balance: "0",
- status: "inactive" as const,
- createdAt: daysAgo(0),
- },
- {
- id: "cust-032",
- name: "Los Pollos Hermanos",
- email: "gus@lospollos.com",
- phone: "(555) 232-3232",
- balance: "98000",
- status: "active" as const,
- createdAt: daysAgo(0),
- },
- ];
- await db.insert(customers).values(seedCustomers);
- // Seed invoices
- await db.insert(invoices).values([
- {
- id: "inv-001",
- customerId: "cust-001",
- customerName: "Acme Corporation",
- amount: "5000",
- status: "paid",
- dueDate: new Date("2024-02-15"),
- items: [
- {
- description: "Consulting Services",
- quantity: 20,
- rate: 250,
- amount: 5000,
- },
- ],
- },
- {
- id: "inv-002",
- customerId: "cust-002",
- customerName: "Globex Industries",
- amount: "3500",
- status: "sent",
- dueDate: new Date("2024-02-28"),
- items: [
- {
- description: "Software License",
- quantity: 7,
- rate: 500,
- amount: 3500,
- },
- ],
- },
- {
- id: "inv-003",
- customerId: "cust-001",
- customerName: "Acme Corporation",
- amount: "10000",
- status: "overdue",
- dueDate: new Date("2024-01-31"),
- items: [
- {
- description: "Annual Support Contract",
- quantity: 1,
- rate: 10000,
- amount: 10000,
- },
- ],
- },
- {
- id: "inv-004",
- customerId: "cust-003",
- customerName: "Initech LLC",
- amount: "2300",
- status: "draft",
- dueDate: new Date("2024-03-15"),
- items: [
- {
- description: "Training Session",
- quantity: 1,
- rate: 2300,
- amount: 2300,
- },
- ],
- },
- {
- id: "inv-005",
- customerId: "cust-004",
- customerName: "Umbrella Corp",
- amount: "45000",
- status: "sent",
- dueDate: new Date("2024-03-01"),
- items: [
- {
- description: "Enterprise License",
- quantity: 1,
- rate: 45000,
- amount: 45000,
- },
- ],
- },
- ]);
- // Seed expenses
- await db.insert(expenses).values([
- {
- id: "exp-001",
- vendor: "AWS",
- category: "Software",
- amount: "2500",
- date: new Date("2024-02-01"),
- status: "approved",
- description: "Cloud hosting - February",
- },
- {
- id: "exp-002",
- vendor: "Office Depot",
- category: "Office Supplies",
- amount: "350",
- date: new Date("2024-02-05"),
- status: "approved",
- description: "Printer paper and toner",
- },
- {
- id: "exp-003",
- vendor: "Delta Airlines",
- category: "Travel",
- amount: "890",
- date: new Date("2024-02-10"),
- status: "pending",
- description: "Flight to client meeting",
- },
- {
- id: "exp-004",
- vendor: "WeWork",
- category: "Rent",
- amount: "4500",
- date: new Date("2024-02-01"),
- status: "approved",
- description: "Office space - February",
- },
- {
- id: "exp-005",
- vendor: "Uber",
- category: "Travel",
- amount: "125",
- date: new Date("2024-02-12"),
- status: "pending",
- description: "Client site visits",
- },
- ]);
- // Seed transactions
- await db.insert(transactions).values([
- {
- id: "txn-001",
- accountId: "acc-001",
- type: "income",
- amount: "5000",
- description: "Invoice #inv-001 payment",
- category: "Sales",
- date: new Date("2024-02-10"),
- },
- {
- id: "txn-002",
- accountId: "acc-001",
- type: "expense",
- amount: "2500",
- description: "AWS hosting",
- category: "Software",
- date: new Date("2024-02-01"),
- },
- {
- id: "txn-003",
- accountId: "acc-003",
- type: "expense",
- amount: "350",
- description: "Office supplies",
- category: "Office",
- date: new Date("2024-02-05"),
- },
- {
- id: "txn-004",
- accountId: "acc-001",
- type: "income",
- amount: "15000",
- description: "Consulting retainer",
- category: "Sales",
- date: new Date("2024-02-01"),
- },
- {
- id: "txn-005",
- accountId: "acc-001",
- type: "expense",
- amount: "4500",
- description: "Office rent",
- category: "Rent",
- date: new Date("2024-02-01"),
- },
- ]);
- return { message: "Database reset and seeded successfully" };
- }
- // Widget methods
- export async function getWidgets() {
- return db.select().from(widgets).orderBy(asc(widgets.order));
- }
- export async function getWidget(id: string) {
- const result = await db
- .select()
- .from(widgets)
- .where(eq(widgets.id, id))
- .limit(1);
- return result[0] || null;
- }
- export async function createWidget(data: { prompt: string; spec: WidgetSpec }) {
- const id = generateId("widget");
- // Get the next order value
- const [result] = await db
- .select({ maxOrder: max(widgets.order) })
- .from(widgets);
- const nextOrder = (result?.maxOrder ?? -1) + 1;
- const [widget] = await db
- .insert(widgets)
- .values({
- id,
- prompt: data.prompt,
- spec: data.spec,
- order: nextOrder,
- })
- .returning();
- return widget;
- }
- export async function updateWidget(
- id: string,
- data: { prompt?: string; spec?: WidgetSpec },
- ) {
- const [widget] = await db
- .update(widgets)
- .set({ ...data, updatedAt: new Date() })
- .where(eq(widgets.id, id))
- .returning();
- return widget || null;
- }
- export async function deleteWidget(id: string) {
- const result = await db
- .delete(widgets)
- .where(eq(widgets.id, id))
- .returning({ id: widgets.id });
- return result.length > 0;
- }
- export async function reorderWidgets(orderedIds: string[]) {
- // Update each widget's order based on position in array
- for (let i = 0; i < orderedIds.length; i++) {
- const id = orderedIds[i];
- if (!id) continue;
- await db.update(widgets).set({ order: i }).where(eq(widgets.id, id));
- }
- }
|