import { eq } from "drizzle-orm"; import dayjs from "dayjs"; import { useDrizzle } from "@/lib/db.js"; import { chargePoint, connector, transaction } from "@/db/schema.js"; import type { StartTransactionRequest, StartTransactionResponse, OcppConnectionContext, } from "../types.ts"; import { resolveIdTagInfo } from "./authorize.ts"; export async function handleStartTransaction( payload: StartTransactionRequest, ctx: OcppConnectionContext, ): Promise { const db = useDrizzle(); // Resolve idTag authorization const idTagInfo = await resolveIdTagInfo(payload.idTag); // Find charge point const [cp] = await db .select({ id: chargePoint.id }) .from(chargePoint) .where(eq(chargePoint.chargePointIdentifier, ctx.chargePointIdentifier)) .limit(1); if (!cp) throw new Error(`ChargePoint not found: ${ctx.chargePointIdentifier}`); // Upsert connector — it may not exist yet if StatusNotification was skipped const [conn] = await db .insert(connector) .values({ id: crypto.randomUUID(), chargePointId: cp.id, connectorId: payload.connectorId, status: "Charging", errorCode: "NoError", lastStatusAt: dayjs().toDate(), }) .onConflictDoUpdate({ target: [connector.chargePointId, connector.connectorId], set: { status: "Charging", updatedAt: dayjs().toDate() }, }) .returning({ id: connector.id }); const rejected = idTagInfo.status !== "Accepted"; const now = dayjs(); // Insert transaction record regardless of auth status (OCPP spec requirement) const [tx] = await db .insert(transaction) .values({ chargePointId: cp.id, connectorId: conn.id, connectorNumber: payload.connectorId, idTag: payload.idTag, idTagStatus: idTagInfo.status, startTimestamp: dayjs(payload.timestamp).toDate(), startMeterValue: payload.meterStart, reservationId: payload.reservationId ?? null, // If rejected, immediately close the transaction so it doesn't appear as in-progress ...(rejected && { stopTimestamp: now.toDate(), stopMeterValue: payload.meterStart, chargeAmount: 0, stopReason: "DeAuthorized", }), }) .returning({ id: transaction.id }); // If rejected, reset connector back to Available if (rejected) { await db .update(connector) .set({ status: "Available", updatedAt: now.toDate() }) .where(eq(connector.id, conn.id)); } console.log( `[OCPP] StartTransaction cp=${ctx.chargePointIdentifier} connector=${payload.connectorId} ` + `idTag=${payload.idTag} status=${idTagInfo.status} txId=${tx.id}`, ); return { transactionId: tx.id, idTagInfo }; }