@@ -40,7 +40,8 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||
actoruser_id: userId,
|
||||
eventtime: normalizeDate(body.eventtime),
|
||||
eventtype: body.eventtype,
|
||||
source: "WEB"
|
||||
source: "WEB",
|
||||
payload: body.payload // Payload (z.B. Description) mit speichern
|
||||
}
|
||||
|
||||
console.log(dataToInsert)
|
||||
@@ -58,6 +59,95 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
// 🆕 POST /staff/time/edit (Bearbeiten durch Invalidieren + Neu erstellen)
|
||||
server.post("/staff/time/edit", async (req, reply) => {
|
||||
try {
|
||||
const userId = req.user.user_id;
|
||||
const tenantId = req.user.tenant_id;
|
||||
|
||||
// Wir erwarten das komplette Paket für die Änderung
|
||||
const {
|
||||
originalEventIds, // Array der IDs, die "gelöscht" werden sollen (Start ID, End ID)
|
||||
newStart, // ISO String
|
||||
newEnd, // ISO String
|
||||
newType, // z.B. 'work', 'vacation'
|
||||
description,
|
||||
reason // Warum wurde geändert? (Audit)
|
||||
} = req.body as {
|
||||
originalEventIds: string[],
|
||||
newStart: string,
|
||||
newEnd: string | null,
|
||||
newType: string,
|
||||
description?: string,
|
||||
reason?: string
|
||||
};
|
||||
|
||||
if (!originalEventIds || originalEventIds.length === 0) {
|
||||
return reply.code(400).send({ error: "Keine Events zum Bearbeiten angegeben." });
|
||||
}
|
||||
|
||||
// 1. Transaction starten (damit alles oder nichts passiert)
|
||||
await server.db.transaction(async (tx) => {
|
||||
|
||||
// A. INVALIDIEREN (Die alten Events "löschen")
|
||||
// Wir erstellen für jedes alte Event ein 'invalidated' Event
|
||||
const invalidations = originalEventIds.map(id => ({
|
||||
tenant_id: tenantId,
|
||||
user_id: userId, // Gehört dem Mitarbeiter
|
||||
actortype: "user",
|
||||
actoruser_id: userId, // Wer hat geändert?
|
||||
eventtime: new Date(),
|
||||
eventtype: "invalidated", // <--- NEUER TYP: Muss in loadValidEvents gefiltert werden!
|
||||
source: "WEB",
|
||||
related_event_id: id, // Zeigt auf das alte Event
|
||||
metadata: {
|
||||
reason: reason || "Bearbeitung",
|
||||
replaced_by_edit: true
|
||||
}
|
||||
}));
|
||||
|
||||
// Batch Insert
|
||||
// @ts-ignore
|
||||
await tx.insert(stafftimeevents).values(invalidations);
|
||||
|
||||
// B. NEU ERSTELLEN (Die korrigierten Events anlegen)
|
||||
|
||||
// Start Event
|
||||
// @ts-ignore
|
||||
await tx.insert(stafftimeevents).values({
|
||||
tenant_id: tenantId,
|
||||
user_id: userId,
|
||||
actortype: "user",
|
||||
actoruser_id: userId,
|
||||
eventtime: new Date(newStart),
|
||||
eventtype: `${newType}_start`, // z.B. work_start
|
||||
source: "WEB",
|
||||
payload: { description: description || "" }
|
||||
});
|
||||
|
||||
// End Event (nur wenn vorhanden)
|
||||
if (newEnd) {
|
||||
// @ts-ignore
|
||||
await tx.insert(stafftimeevents).values({
|
||||
tenant_id: tenantId,
|
||||
user_id: userId,
|
||||
actortype: "user",
|
||||
actoruser_id: userId,
|
||||
eventtime: new Date(newEnd),
|
||||
eventtype: `${newType}_end`, // z.B. work_end
|
||||
source: "WEB"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
|
||||
} catch (err: any) {
|
||||
console.error("Fehler beim Bearbeiten:", err);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /staff/time/submit
|
||||
server.post("/staff/time/submit", async (req, reply) => {
|
||||
try {
|
||||
@@ -209,6 +299,7 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||
const endDate = new Date("2100-12-31");
|
||||
|
||||
// SCHRITT 1: Lade ALLE Events für den ZIEL-USER (evaluatedUserId)
|
||||
// WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern!
|
||||
const allEventsInTimeFrame = await loadValidEvents(
|
||||
server,
|
||||
tenantId,
|
||||
@@ -283,6 +374,7 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||
// --- 3. Ausführung der Logik für den ermittelten Benutzer ---
|
||||
|
||||
// SCHRITT 1: Lade ALLE gültigen Events im Zeitraum
|
||||
// WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern!
|
||||
const allEventsInTimeFrame = await loadValidEvents(
|
||||
server, tenantId, evaluatedUserId, startDate, endDate // Verwendung der evaluatedUserId
|
||||
);
|
||||
@@ -335,226 +427,4 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*// -------------------------------------------------------------
|
||||
// ▶ Neue Zeit starten
|
||||
// -------------------------------------------------------------
|
||||
server.post("/staff/time", async (req, reply) => {
|
||||
try {
|
||||
const userId = req.user.user_id
|
||||
const tenantId = req.user.tenant_id
|
||||
|
||||
const body = req.body as any
|
||||
|
||||
const normalizeDate = (val: any) => {
|
||||
if (!val) return null
|
||||
const d = new Date(val)
|
||||
return isNaN(d.getTime()) ? null : d
|
||||
}
|
||||
|
||||
const dataToInsert = {
|
||||
tenant_id: tenantId,
|
||||
user_id: body.user_id || userId,
|
||||
type: body.type || "work",
|
||||
description: body.description || null,
|
||||
started_at: normalizeDate(body.started_at),
|
||||
stopped_at: normalizeDate(body.stopped_at),
|
||||
}
|
||||
|
||||
const [created] = await server.db
|
||||
.insert(stafftimeentries)
|
||||
.values(dataToInsert)
|
||||
.returning()
|
||||
|
||||
return created
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
return reply.code(400).send({ error: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Zeit stoppen
|
||||
// -------------------------------------------------------------
|
||||
server.put<{
|
||||
Params: { id: string },
|
||||
Body: { stopped_at: string }
|
||||
}>("/staff/time/:id/stop", async (req, reply) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { stopped_at } = req.body
|
||||
|
||||
// Normalize timestamp
|
||||
const normalizeDate = (val: any) => {
|
||||
const d = new Date(val)
|
||||
return isNaN(d.getTime()) ? null : d
|
||||
}
|
||||
|
||||
const stopTime = normalizeDate(stopped_at)
|
||||
if (!stopTime) {
|
||||
return reply.code(400).send({ error: "Invalid stopped_at timestamp" })
|
||||
}
|
||||
|
||||
const [updated] = await server.db
|
||||
.update(stafftimeentries)
|
||||
.set({
|
||||
stopped_at: stopTime,
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.where(eq(stafftimeentries.id, id))
|
||||
.returning()
|
||||
|
||||
if (!updated) {
|
||||
return reply.code(404).send({ error: "Time entry not found" })
|
||||
}
|
||||
|
||||
return reply.send(updated)
|
||||
} catch (err: any) {
|
||||
console.error("STOP ERROR:", err)
|
||||
return reply.code(500).send({ error: err.message || "Internal server error" })
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Liste aller Zeiten
|
||||
// -------------------------------------------------------------
|
||||
server.get("/staff/time", async (req, reply) => {
|
||||
try {
|
||||
const { from, to, type, user_id } = req.query as any
|
||||
const { tenant_id, user_id: currentUserId } = req.user
|
||||
|
||||
let where = and(eq(stafftimeentries.tenant_id, tenant_id))
|
||||
|
||||
// Zugriffsbeschränkung
|
||||
if (!req.hasPermission("staff.time.read_all")) {
|
||||
where = and(where, eq(stafftimeentries.user_id, currentUserId))
|
||||
} else if (user_id) {
|
||||
where = and(where, eq(stafftimeentries.user_id, user_id))
|
||||
}
|
||||
|
||||
if (from) where = and(where, gte(stafftimeentries.started_at, from))
|
||||
if (to) where = and(where, lte(stafftimeentries.started_at, to))
|
||||
if (type) where = and(where, eq(stafftimeentries.type, type))
|
||||
|
||||
const rows = await server.db
|
||||
.select()
|
||||
.from(stafftimeentries)
|
||||
.where(where)
|
||||
.orderBy(desc(stafftimeentries.started_at))
|
||||
|
||||
return rows
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return reply.code(400).send({ error: (err as Error).message })
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Einzelne Zeit (inkl. Connects)
|
||||
// -------------------------------------------------------------
|
||||
server.get("/staff/time/:id", async (req, reply) => {
|
||||
try {
|
||||
const { id } = req.params as any
|
||||
|
||||
const rows = await server.db
|
||||
.select()
|
||||
.from(stafftimeentries)
|
||||
.where(eq(stafftimeentries.id, id))
|
||||
.limit(1)
|
||||
|
||||
if (!rows.length) return reply.code(404).send({ error: "Not found" })
|
||||
|
||||
const entry = rows[0]
|
||||
|
||||
const connects = await server.db
|
||||
.select()
|
||||
.from(stafftimenetryconnects)
|
||||
.where(eq(stafftimenetryconnects.stafftimeentry, id))
|
||||
|
||||
return {
|
||||
...entry,
|
||||
staff_time_entry_connects: connects
|
||||
}
|
||||
} catch (err) {
|
||||
return reply.code(400).send({ error: (err as Error).message })
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Zeit bearbeiten
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Zeit bearbeiten
|
||||
server.put<{
|
||||
Params: { id: string },
|
||||
}>("/staff/time/:id", async (req, reply) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const body = req.body
|
||||
|
||||
// Normalize all timestamp fields
|
||||
const normalizeDate = (val: any) => {
|
||||
if (!val) return null
|
||||
const d = new Date(val)
|
||||
return isNaN(d.getTime()) ? null : d
|
||||
}
|
||||
|
||||
|
||||
const updateData: any = {
|
||||
// @ts-ignore
|
||||
...body,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
// Only convert if present — avoid overriding with null unless sent
|
||||
// @ts-ignore
|
||||
if (body.started_at !== undefined) {
|
||||
// @ts-ignore
|
||||
updateData.started_at = normalizeDate(body.started_at)
|
||||
}
|
||||
// @ts-ignore
|
||||
if (body.stopped_at !== undefined) {
|
||||
// @ts-ignore
|
||||
updateData.stopped_at = normalizeDate(body.stopped_at)
|
||||
}
|
||||
// @ts-ignore
|
||||
if (body.approved_at !== undefined) {
|
||||
// @ts-ignore
|
||||
updateData.approved_at = normalizeDate(body.approved_at)
|
||||
}
|
||||
|
||||
const [updated] = await server.db
|
||||
.update(stafftimeentries)
|
||||
.set(updateData)
|
||||
.where(eq(stafftimeentries.id, id))
|
||||
.returning()
|
||||
|
||||
if (!updated) {
|
||||
return reply.code(404).send({ error: "Time entry not found" })
|
||||
}
|
||||
|
||||
return reply.send(updated)
|
||||
} catch (err: any) {
|
||||
console.error("UPDATE ERROR:", err)
|
||||
return reply.code(500).send({ error: err.message || "Internal server error" })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ▶ Zeit löschen
|
||||
// -------------------------------------------------------------
|
||||
server.delete("/staff/time/:id", async (req, reply) => {
|
||||
try {
|
||||
const { id } = req.params as any
|
||||
|
||||
await server.db
|
||||
.delete(stafftimeentries)
|
||||
.where(eq(stafftimeentries.id, id))
|
||||
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
return reply.code(400).send({ error: (err as Error).message })
|
||||
}
|
||||
})*/
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user