import type { Address, AuditEvent, OrderDetail, OrderItemDetail, OrderSummary } from "@orderops/shared"; import { and, asc, desc, eq, inArray, or, sql } from "drizzle-orm"; import type { DatabaseContext } from "../db/database"; import { addresses, auditEvents, inventory, orderItems, orders, products, users } from "../db/schema"; function toAddress(row: typeof addresses.$inferSelect): Address { return { fullName: row.fullName, line1: row.line1, line2: row.line2, city: row.city, state: row.state, postalCode: row.postalCode, country: row.country }; } function toOrderItemDetail(row: { item: typeof orderItems.$inferSelect; product: typeof products.$inferSelect; inventory: typeof inventory.$inferSelect | null; }): OrderItemDetail { return { id: row.item.id, productId: row.product.id, sku: row.product.sku, productName: row.product.name, quantity: row.item.quantity, unitPrice: { amountCents: row.item.unitPriceCents, currency: "USD" }, availableQuantity: row.inventory?.availableQuantity ?? 0 }; } export async function listOrderSummaries(context: DatabaseContext, query?: string) { const search = query?.trim().toLowerCase(); const filters = search ? or( sql`instr(lower(${orders.orderNumber}), ${search}) > 0`, sql`instr(lower(${users.name}), ${search}) > 0`, sql`instr(lower(${users.email}), ${search}) > 0` ) : undefined; const rows = await context.db .select({ order: orders, customer: users, itemCount: sql`count(${orderItems.id})` }) .from(orders) .innerJoin(users, eq(orders.customerId, users.id)) .leftJoin(orderItems, eq(orderItems.orderId, orders.id)) .where(filters) .groupBy(orders.id, users.id) .orderBy(desc(orders.createdAt)); return rows.map(({ order, customer, itemCount }) => ({ id: order.id, orderNumber: order.orderNumber, status: order.status as OrderSummary["status"], customer: { id: customer.id, name: customer.name, email: customer.email }, itemCount, createdAt: order.createdAt })) satisfies OrderSummary[]; } export async function findOrderDetail(context: DatabaseContext, orderId: string) { const [header] = await context.db .select({ order: orders, customer: users, address: addresses }) .from(orders) .innerJoin(users, eq(orders.customerId, users.id)) .innerJoin(addresses, eq(orders.shippingAddressId, addresses.id)) .where(eq(orders.id, orderId)); if (!header) { return null; } const itemRows = await context.db .select({ item: orderItems, product: products, inventory }) .from(orderItems) .innerJoin(products, eq(orderItems.productId, products.id)) .leftJoin(inventory, eq(orderItems.productId, inventory.productId)) .where(eq(orderItems.orderId, orderId)) .orderBy(asc(orderItems.id)); return { id: header.order.id, orderNumber: header.order.orderNumber, status: header.order.status as OrderDetail["status"], notes: header.order.notes, createdAt: header.order.createdAt, updatedAt: header.order.updatedAt, customer: { id: header.customer.id, name: header.customer.name, email: header.customer.email }, shippingAddress: toAddress(header.address), items: itemRows.map(toOrderItemDetail) } satisfies OrderDetail; } export async function findOrderItemRecord(context: DatabaseContext, orderId: string, itemId: string) { const [row] = await context.db .select({ order: orders, item: orderItems, inventory }) .from(orderItems) .innerJoin(orders, eq(orderItems.orderId, orders.id)) .leftJoin(inventory, eq(orderItems.productId, inventory.productId)) .where(and(eq(orderItems.id, itemId), eq(orderItems.orderId, orderId))); return row ?? null; } export async function updateOrderItemQuantity(context: DatabaseContext, itemId: string, quantity: number) { await context.db.update(orderItems).set({ quantity }).where(eq(orderItems.id, itemId)); } export async function updateOrderTimestamp(context: DatabaseContext, orderId: string, updatedAt: string) { await context.db.update(orders).set({ updatedAt }).where(eq(orders.id, orderId)); } export async function updateOrderNotes(context: DatabaseContext, orderId: string, notes: string, updatedAt: string) { await context.db.update(orders).set({ notes, updatedAt }).where(eq(orders.id, orderId)); } export async function updateOrderStatus(context: DatabaseContext, orderId: string, status: OrderDetail["status"], updatedAt: string) { await context.db.update(orders).set({ status, updatedAt }).where(eq(orders.id, orderId)); } export async function updateOrderAddress(context: DatabaseContext, orderId: string, address: Address) { const [row] = await context.db.select({ shippingAddressId: orders.shippingAddressId }).from(orders).where(eq(orders.id, orderId)); if (!row) { return false; } await context.db .update(addresses) .set({ fullName: address.fullName, line1: address.line1, line2: address.line2, city: address.city, state: address.state, postalCode: address.postalCode, country: address.country }) .where(eq(addresses.id, row.shippingAddressId)); return true; } export async function appendAuditEvent( context: DatabaseContext, event: { orderId: string; actor: string; type: string; message: string; createdAt: string; } ) { await context.db.insert(auditEvents).values({ id: crypto.randomUUID(), orderId: event.orderId, actor: event.actor, type: event.type, message: event.message, createdAt: event.createdAt }); } async function findCustomerSummaryByOrderId(context: DatabaseContext, orderId: string) { const [row] = await context.db .select({ customer: users }) .from(orders) .innerJoin(users, eq(orders.customerId, users.id)) .where(eq(orders.id, orderId)); return row?.customer ?? null; } export async function listOrderAuditEvents(context: DatabaseContext, orderId: string) { const events = await context.db.select().from(auditEvents).where(eq(auditEvents.orderId, orderId)).orderBy(desc(auditEvents.createdAt)); return Promise.all( events.map(async (event) => { await findCustomerSummaryByOrderId(context, event.orderId); return { id: event.id, orderId: event.orderId, type: event.type, message: event.message, createdAt: event.createdAt, actor: event.actor } satisfies AuditEvent; }) ); } export async function listOrdersByIds(context: DatabaseContext, orderIds: string[]) { return context.db.select().from(orders).where(inArray(orders.id, orderIds)); }