feat(time): add span-based attendance tracking under 2.0

This commit is contained in:
2026-10-03 15:55:45 +02:00
parent 4c67ddd908
commit 677ae0f4fb
32 changed files with 1681 additions and 47 deletions

View File

@@ -1,3 +1,6 @@
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";
@@ -8,7 +11,8 @@ export default async function devicesRFIDRoutes(server: FastifyInstance) {
async (req, reply) => {
try {
// 1. Timestamp aus dem Body holen (optional)
const { rfid_id, timestamp } = req.body as {
const { rfid_id, timestamp, request_id } = req.body as {
request_id?: string,
rfid_id: string,
timestamp?: number // Kann undefined sein (Live) oder Zahl (Offline)
};
@@ -51,11 +55,28 @@ export default async function devicesRFIDRoutes(server: FastifyInstance) {
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(eq(stafftimeevents.user_id, profile.user_id))
.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]);