New Staff Functions
This commit is contained in:
@@ -2,36 +2,8 @@ import { FastifyInstance } from "fastify";
|
|||||||
|
|
||||||
export default async function authProfilesRoutes(server: FastifyInstance) {
|
export default async function authProfilesRoutes(server: FastifyInstance) {
|
||||||
// Ein einzelnes Profil laden (nur im aktuellen Tenant)
|
// Ein einzelnes Profil laden (nur im aktuellen Tenant)
|
||||||
server.get<{
|
server.get("/profiles/:id", async (req, reply) => {
|
||||||
Params: { id: string }
|
const { id } = req.params as {id:string};
|
||||||
}>("/api/profiles/:id", {
|
|
||||||
schema: {
|
|
||||||
tags: ["Auth"],
|
|
||||||
summary: "Get a profile by user id (only from current tenant)",
|
|
||||||
params: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "string", format: "uuid" }
|
|
||||||
},
|
|
||||||
required: ["id"]
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
200: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "string" },
|
|
||||||
firstName: { type: "string" },
|
|
||||||
lastName: { type: "string" },
|
|
||||||
email: { type: "string", nullable: true },
|
|
||||||
mobileTel: { type: "string", nullable: true },
|
|
||||||
fixedTel: { type: "string", nullable: true },
|
|
||||||
role: { type: "string", nullable: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, async (req, reply) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const tenantId = (req.user as any)?.tenant_id;
|
const tenantId = (req.user as any)?.tenant_id;
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
@@ -39,19 +11,44 @@ export default async function authProfilesRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const { data, error } = await server.supabase
|
||||||
.from("auth_users")
|
.from("auth_profiles")
|
||||||
.select("*, profile(*)")
|
.select()
|
||||||
.eq("id", id)
|
.eq("id", id)
|
||||||
.eq("tenant", tenantId)
|
.eq("tenant_id", tenantId)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
|
console.log(error)
|
||||||
return reply.code(404).send({ error: "User not found or not in tenant" });
|
return reply.code(404).send({ error: "User not found or not in tenant" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
console.log(data);
|
||||||
user_id: data.id,
|
|
||||||
...data.profile
|
reply.send(data)
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.put("/profiles/:id", async (req, reply) => {
|
||||||
|
if (!req.user.tenant_id) {
|
||||||
|
return reply.code(400).send({ error: "No tenant selected" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params as { id: string };
|
||||||
|
const body = req.body
|
||||||
|
|
||||||
|
delete body.full_name
|
||||||
|
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from("auth_profiles")
|
||||||
|
.update(body)
|
||||||
|
.eq("id", id)
|
||||||
|
.eq("tenant_id", req.user.tenant_id)
|
||||||
|
.select("*")
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
console.log(error)
|
||||||
|
return reply.code(404).send({ error: "User not found or not in tenant" });
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
135
src/routes/staff/time.ts
Normal file
135
src/routes/staff/time.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { FastifyInstance } from 'fastify'
|
||||||
|
import { StaffTimeEntry } from '../../types/staff'
|
||||||
|
|
||||||
|
export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||||
|
|
||||||
|
// ▶ Neue Zeit starten
|
||||||
|
server.post<{ Body: Pick<StaffTimeEntry, 'started_at' | 'stopped_at' | 'type' | 'description'> }>(
|
||||||
|
'/staff/time',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { started_at, stopped_at, type = 'work', description } = req.body
|
||||||
|
const userId = req.user.user_id
|
||||||
|
const tenantId = req.user.tenant_id
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.insert([{ tenant_id: tenantId, user_id: userId, started_at, stopped_at, type, description }])
|
||||||
|
.select()
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Zeit stoppen
|
||||||
|
server.put<{ Params: { id: string }, Body: { stopped_at: string } }>(
|
||||||
|
'/staff/time/:id/stop',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const { stopped_at } = req.body
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.update({ stopped_at, updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', id)
|
||||||
|
.select()
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Liste aller Zeiten
|
||||||
|
server.get<{
|
||||||
|
Querystring: {
|
||||||
|
from?: string
|
||||||
|
to?: string
|
||||||
|
type?: string
|
||||||
|
user_id?: string
|
||||||
|
}
|
||||||
|
}>('/staff/time', async (req, reply) => {
|
||||||
|
const { from, to, type, user_id } = req.query
|
||||||
|
const { user_id: currentUserId, tenant_id } = req.user
|
||||||
|
|
||||||
|
// 🧩 Basis-Query für den Tenant
|
||||||
|
let query = server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.select('*')
|
||||||
|
.eq('tenant_id', tenant_id)
|
||||||
|
.order('started_at', { ascending: false })
|
||||||
|
|
||||||
|
// 🔒 Zugriffsbeschränkung: nur eigene Zeiten, außer Berechtigung erlaubt mehr
|
||||||
|
if (!req.hasPermission('staff.time.read_all')) {
|
||||||
|
query = query.eq('user_id', currentUserId)
|
||||||
|
} else if (user_id) {
|
||||||
|
// falls explizit user_id angegeben wurde
|
||||||
|
query = query.eq('user_id', user_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📅 Zeitfilter
|
||||||
|
if (from) query = query.gte('started_at', from)
|
||||||
|
if (to) query = query.lte('started_at', to)
|
||||||
|
if (type) query = query.eq('type', type)
|
||||||
|
|
||||||
|
const { data, error } = await query
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
|
||||||
|
return reply.send(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// ▶ Einzelne Zeit abrufen (inkl. Connects)
|
||||||
|
server.get<{ Params: { id: string } }>(
|
||||||
|
'/staff/time/:id',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
staff_time_entry_connects(*)
|
||||||
|
`)
|
||||||
|
.eq('id', id)
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Zeit bearbeiten
|
||||||
|
server.put<{ Params: { id: string }, Body: Partial<StaffTimeEntry> }>(
|
||||||
|
'/staff/time/:id',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.update({ ...req.body, updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', id)
|
||||||
|
.select()
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Zeit löschen
|
||||||
|
server.delete<{ Params: { id: string } }>(
|
||||||
|
'/staff/time/:id',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const { error } = await server.supabase
|
||||||
|
.from('staff_time_entries')
|
||||||
|
.delete()
|
||||||
|
.eq('id', id)
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send({ success: true })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
71
src/routes/staff/timeconnects.ts
Normal file
71
src/routes/staff/timeconnects.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { FastifyInstance } from 'fastify'
|
||||||
|
import { StaffTimeEntryConnect } from '../../types/staff'
|
||||||
|
|
||||||
|
export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
||||||
|
|
||||||
|
// ▶ Connect anlegen
|
||||||
|
server.post<{ Params: { id: string }, Body: Omit<StaffTimeEntryConnect, 'id' | 'time_entry_id'> }>(
|
||||||
|
'/staff/time/:id/connects',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const { started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes } = req.body
|
||||||
|
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entry_connects')
|
||||||
|
.insert([{ time_entry_id: id, started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes }])
|
||||||
|
.select()
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Connects abrufen
|
||||||
|
server.get<{ Params: { id: string } }>(
|
||||||
|
'/staff/time/:id/connects',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entry_connects')
|
||||||
|
.select('*')
|
||||||
|
.eq('time_entry_id', id)
|
||||||
|
.order('started_at', { ascending: true })
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Connect aktualisieren
|
||||||
|
server.patch<{ Params: { connectId: string }, Body: Partial<StaffTimeEntryConnect> }>(
|
||||||
|
'/staff/time/connects/:connectId',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { connectId } = req.params
|
||||||
|
const { data, error } = await server.supabase
|
||||||
|
.from('staff_time_entry_connects')
|
||||||
|
.update({ ...req.body, updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', connectId)
|
||||||
|
.select()
|
||||||
|
.maybeSingle()
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ▶ Connect löschen
|
||||||
|
server.delete<{ Params: { connectId: string } }>(
|
||||||
|
'/staff/time/connects/:connectId',
|
||||||
|
async (req, reply) => {
|
||||||
|
const { connectId } = req.params
|
||||||
|
const { error } = await server.supabase
|
||||||
|
.from('staff_time_entry_connects')
|
||||||
|
.delete()
|
||||||
|
.eq('id', connectId)
|
||||||
|
|
||||||
|
if (error) return reply.code(400).send({ error: error.message })
|
||||||
|
return reply.send({ success: true })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
27
src/types/staff.ts
Normal file
27
src/types/staff.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export interface StaffTimeEntry {
|
||||||
|
id: string
|
||||||
|
tenant_id: string
|
||||||
|
user_id: string
|
||||||
|
started_at: string
|
||||||
|
stopped_at?: string | null
|
||||||
|
duration_minutes?: number | null
|
||||||
|
type: 'work' | 'break' | 'absence' | 'other'
|
||||||
|
description?: string | null
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StaffTimeEntryConnect {
|
||||||
|
id: string
|
||||||
|
time_entry_id: string
|
||||||
|
project_id?: string | null
|
||||||
|
customer_id?: string | null
|
||||||
|
task_id?: string | null
|
||||||
|
ticket_id?: string | null
|
||||||
|
started_at: string
|
||||||
|
stopped_at: string
|
||||||
|
duration_minutes?: number
|
||||||
|
notes?: string | null
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user