123 lines
5.6 KiB
TypeScript
123 lines
5.6 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { sql } from "drizzle-orm";
|
|
import { TimeService, digest, rows } from "../../modules/time-v2/service";
|
|
import { FastifyInstance } from "fastify";
|
|
import { and, desc, eq } from "drizzle-orm";
|
|
import { authProfiles, devices, stafftimeevents } from "../../../db/schema";
|
|
|
|
export default async function devicesRFIDRoutes(server: FastifyInstance) {
|
|
server.post(
|
|
"/rfid/createevent/:terminal_id",
|
|
async (req, reply) => {
|
|
try {
|
|
// 1. Timestamp aus dem Body holen (optional)
|
|
const { rfid_id, timestamp, request_id } = req.body as {
|
|
request_id?: string,
|
|
rfid_id: string,
|
|
timestamp?: number // Kann undefined sein (Live) oder Zahl (Offline)
|
|
};
|
|
|
|
const { terminal_id } = req.params as { terminal_id: string };
|
|
|
|
if (!rfid_id || !terminal_id) {
|
|
console.log(`Missing Params`);
|
|
return reply.code(400).send(`Missing Params`);
|
|
}
|
|
|
|
// 2. Gerät suchen
|
|
const device = await server.db
|
|
.select()
|
|
.from(devices)
|
|
.where(eq(devices.externalId, terminal_id))
|
|
.limit(1)
|
|
.then(rows => rows[0]);
|
|
|
|
if (!device) {
|
|
console.log(`Device ${terminal_id} not found`);
|
|
return reply.code(400).send(`Device ${terminal_id} not found`);
|
|
}
|
|
|
|
// 3. User-Profil suchen
|
|
const profile = await server.db
|
|
.select()
|
|
.from(authProfiles)
|
|
.where(
|
|
and(
|
|
eq(authProfiles.tenant_id, device.tenant),
|
|
eq(authProfiles.token_id, rfid_id),
|
|
eq(authProfiles.active, true)
|
|
)
|
|
)
|
|
.limit(1)
|
|
.then(rows => rows[0]);
|
|
|
|
if (!profile) {
|
|
console.log(`Profile for Token ${rfid_id} not found`);
|
|
return reply.code(400).send(`Profile for Token ${rfid_id} not found`);
|
|
}
|
|
|
|
const timeService = new TimeService(server.db);
|
|
const actor = { tenant: device.tenant, user: profile.user_id, trusted: true };
|
|
if (profile.user_id && await timeService.active(actor, profile.user_id)) {
|
|
return await server.db.transaction(async tx => {
|
|
await timeService.lock(tx, actor, profile.user_id);
|
|
const scoped = new TimeService(tx);
|
|
const at = timestamp && timestamp > 0 ? new Date(timestamp * 1000).toISOString() : new Date().toISOString();
|
|
const key = `rfid:${digest({ terminal_id, rfid_id, event: request_id || timestamp || randomUUID() })}`;
|
|
const [cached] = await rows(tx, sql`SELECT response FROM staff_time_v2_requests WHERE tenant_id=${device.tenant} AND user_id=${profile.user_id}::uuid AND request_id=${key}`);
|
|
if (cached) return cached.response;
|
|
const current = await scoped.current(actor, profile.user_id);
|
|
return scoped.mutate(actor, profile.user_id, key, current
|
|
? { action: 'stop', id: current.id, version: current.version, at }
|
|
: { action: 'start', at }, 'terminal');
|
|
});
|
|
}
|
|
|
|
// 4. Letztes Event suchen (für Status-Toggle Work Start/End)
|
|
const lastEvent = await server.db
|
|
.select()
|
|
.from(stafftimeevents)
|
|
.where(and(eq(stafftimeevents.user_id, profile.user_id), eq(stafftimeevents.tenant_id, device.tenant)))
|
|
.orderBy(desc(stafftimeevents.eventtime))
|
|
.limit(1)
|
|
.then(rows => rows[0]);
|
|
|
|
// 5. Zeitstempel Logik (WICHTIG!)
|
|
// Der ESP32 sendet Unix-Timestamp in SEKUNDEN. JS braucht MILLISEKUNDEN.
|
|
// Wenn kein Timestamp kommt (0 oder undefined), nehmen wir JETZT.
|
|
const actualEventTime = (timestamp && timestamp > 0)
|
|
? new Date(timestamp * 1000)
|
|
: new Date();
|
|
|
|
// 6. Event Typ bestimmen (Toggle Logik)
|
|
// Falls noch nie gestempelt wurde (lastEvent undefined), fangen wir mit start an.
|
|
const nextEventType = (lastEvent?.eventtype === "work_start")
|
|
? "work_end"
|
|
: "work_start";
|
|
|
|
const dataToInsert = {
|
|
tenant_id: device.tenant,
|
|
user_id: profile.user_id,
|
|
actortype: "system",
|
|
eventtime: actualEventTime, // Hier nutzen wir die berechnete Zeit
|
|
eventtype: nextEventType,
|
|
source: "TERMINAL" // Habe ich von WEB auf TERMINAL geändert (optional)
|
|
};
|
|
|
|
console.log(`New Event for ${profile.user_id}: ${nextEventType} @ ${actualEventTime.toISOString()}`);
|
|
|
|
const [created] = await server.db
|
|
.insert(stafftimeevents)
|
|
//@ts-ignore
|
|
.values(dataToInsert)
|
|
.returning();
|
|
|
|
return created;
|
|
|
|
} catch (err: any) {
|
|
console.error(err);
|
|
return reply.code(400).send({ error: err.message });
|
|
}
|
|
}
|
|
);
|
|
} |