87 lines
2.7 KiB
TypeScript
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 };
|
|
}
|