Files
helios-evcs/apps/csms/src/ocpp/actions/start-transaction.ts

87 lines
2.7 KiB
TypeScript

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<StartTransactionResponse> {
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 };
}