/** * human-tool 存储 adapter —— Drizzle 只准出现在 adapter(编码规范 §5.3)。 */ import { and, asc, eq, isNull, sql } from 'drizzle-orm'; import type { ChatCard, StaffId, TaskId, WorkOrder } from '@contracts'; import { chatCardSchema, workOrderSchema } from '@contracts'; import type { Db } from '@shared/db.ts'; import type { CardStore } from '../../port.ts'; import { chatCardTable, workOrderTable } from './human-tool-schema.ts'; export function createCardStore(db: Db): CardStore { return { async saveWorkOrder(taskId, order, recipientId) { await db.insert(workOrderTable).values({ id: order.id, taskId, recipientId, mode: order.mode, title: order.title, payload: order, at: new Date(order.at), done: order.done ?? null, }); }, async getWorkOrder(id) { const rows = await db.select().from(workOrderTable).where(eq(workOrderTable.id, id)).limit(1); const row = rows[0]; if (row === undefined) return undefined; const order = workOrderSchema.parse({ ...(row.payload as Record), ...(row.done === null ? {} : { done: row.done }), }); return { taskId: row.taskId as TaskId, order, recipientId: row.recipientId as StaffId }; }, async listWorkOrders(taskId, openOnly) { const conditions = [eq(workOrderTable.taskId, taskId)]; if (openOnly) conditions.push(isNull(workOrderTable.done)); const rows = await db .select() .from(workOrderTable) .where(and(...conditions)) .orderBy(asc(workOrderTable.at)); return rows.map((row) => workOrderSchema.parse({ ...(row.payload as Record), ...(row.done === null ? {} : { done: row.done }), }), ); }, async markWorkOrderDone(id, done) { await db.update(workOrderTable).set({ done }).where(eq(workOrderTable.id, id)); }, /* * 局部改卡面:整卡存在 `payload` 里,所以读回来合并再写回。 * 只对**未回传**的卡开放(调用方保证)—— 已答的卡是留痕,只增不改。 */ async patchWorkOrder(id, patch) { const rows = await db.select().from(workOrderTable).where(eq(workOrderTable.id, id)).limit(1); const row = rows[0]; if (row === undefined) return; await db .update(workOrderTable) .set({ payload: { ...(row.payload as Record), ...patch } }) .where(eq(workOrderTable.id, id)); }, async saveChatCard(taskId, card, recipientId) { await db.insert(chatCardTable).values({ id: card.id, taskId, recipientId, kind: card.k, payload: card, at: new Date(card.at), }); }, async getChatCard(id) { const rows = await db.select().from(chatCardTable).where(eq(chatCardTable.id, id)).limit(1); const row = rows[0]; if (row === undefined) return undefined; return { taskId: row.taskId as TaskId, card: chatCardSchema.parse(row.payload), recipientId: row.recipientId as StaffId, }; }, async listChatCards(taskId) { const rows = await db .select() .from(chatCardTable) .where(eq(chatCardTable.taskId, taskId)) .orderBy(asc(chatCardTable.at)); return rows.map((row) => chatCardSchema.parse(row.payload)); }, async listOpenChatCards(taskId) { const rows = await db .select() .from(chatCardTable) .where(and(eq(chatCardTable.taskId, taskId), isNull(chatCardTable.supersededBy))) .orderBy(asc(chatCardTable.at)); return rows.map((row) => chatCardSchema.parse(row.payload)); }, async updateChatCard(card: ChatCard) { await db.update(chatCardTable).set({ payload: card }).where(eq(chatCardTable.id, card.id)); }, async closeChatCard(id, supersededBy, reason) { await db.update(chatCardTable).set({ supersededBy, rejectedReason: reason }).where(eq(chatCardTable.id, id)); }, /** 调人次数 = 发出的工单 + 对话卡 —— 打扰率与预算的计数面(OS 8.3) */ async humanCallCount(taskId) { const orders = await db .select({ n: sql`count(*)::int` }) .from(workOrderTable) .where(eq(workOrderTable.taskId, taskId)); const cards = await db .select({ n: sql`count(*)::int` }) .from(chatCardTable) .where(eq(chatCardTable.taskId, taskId)); return (orders[0]?.n ?? 0) + (cards[0]?.n ?? 0); }, async openCardsOf(staffId) { const orders = await db .select({ id: workOrderTable.id, title: workOrderTable.title }) .from(workOrderTable) .where(and(eq(workOrderTable.recipientId, staffId), isNull(workOrderTable.done))); const cards = await db .select({ id: chatCardTable.id, kind: chatCardTable.kind }) .from(chatCardTable) .where(and(eq(chatCardTable.recipientId, staffId), isNull(chatCardTable.supersededBy))); return [ ...orders.map((o) => ({ cardId: o.id, title: o.title })), ...cards.map((c) => ({ cardId: c.id, title: `${c.kind} 卡` })), ]; }, }; } export type { WorkOrder };