init
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
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();
|
||||
|
||||
const filters = search
|
||||
? or(
|
||||
sql`instr(${orders.orderNumber}, ${search}) > 0`,
|
||||
sql`instr(${users.name}, ${search}) > 0`,
|
||||
sql`instr(${users.email}, ${search}) > 0`
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const rows = await context.db
|
||||
.select({
|
||||
order: orders,
|
||||
customer: users,
|
||||
itemCount: sql<number>`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"],
|
||||
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 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));
|
||||
}
|
||||
Reference in New Issue
Block a user