63ef43ec-90ce-4fcf-82b4-abff91b2280a

This commit is contained in:
Z User committed 2026-10-27 01:02:02 +00:00
1 parent 8fa25577c0
commit c71c7e3094
12 files changed
+578 -9

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ export async function POST() {
await db.apiToken.deleteMany()
await db.portalApproval.deleteMany()
await db.article.deleteMany()
await db.taskComment.deleteMany()
await db.estimateLine.deleteMany()
await db.estimate.deleteMany()
await db.ticketMessage.deleteMany()
@@ -0,0 +1,44 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
// GET /api/aos/tasks/[id]/comments — list comments on a task
export async function GET(
_req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const comments = await db.taskComment.findMany({
where: { taskId: id },
orderBy: { createdAt: 'asc' },
})
return NextResponse.json({ comments })
}
// POST /api/aos/tasks/[id]/comments — add a comment
export async function POST(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const { body, author } = (await req.json()) as { body: string; author?: string }
if (!body?.trim()) return NextResponse.json({ error: 'body required' }, { status: 400 })
const comment = await db.taskComment.create({
data: { taskId: id, body: body.trim(), author: author ?? 'Max Brandt' },
})
await db.auditLog.create({
data: {
actor: author ?? 'max@nordlicht.digital',
action: 'task.comment:create',
entity: 'TaskComment',
entityId: comment.id,
after: JSON.stringify({ taskId: id, length: body.length }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({ comment })
}
+140
View File
@@ -0,0 +1,140 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
// GET /api/aos/timesheet?week=YYYY-MM-DD
// Returns a weekly timesheet grid: projects × days (Mon-Sun)
// with time entries per cell + totals + lock status.
export async function GET(req: Request) {
const { searchParams } = new URL(req.url)
const weekParam = searchParams.get('week') // a date within the target week (YYYY-MM-DD)
const now = new Date()
let refDate = now
if (weekParam) {
const d = new Date(weekParam + 'T00:00:00Z')
if (!isNaN(d.getTime())) refDate = d
}
// Calculate the Monday of the target week (ISO week, Monday start)
const day = refDate.getDay()
const offset = day === 0 ? -6 : 1 - day // Sunday=0 → -6; Monday=1 → 0; etc.
const monday = new Date(refDate)
monday.setDate(refDate.getDate() + offset)
monday.setHours(0, 0, 0, 0)
const days: Date[] = []
for (let i = 0; i < 7; i++) {
const d = new Date(monday)
d.setDate(monday.getDate() + i)
days.push(d)
}
const sunday = new Date(days[6]!)
sunday.setHours(23, 59, 59, 999)
// Fetch all time entries in this week
const entries = await db.timeEntry.findMany({
where: { date: { gte: monday, lte: sunday } },
include: { project: { select: { id: true, name: true } } },
orderBy: { date: 'asc' },
})
// Group by project
const projectMap = new Map<string, { id: string; name: string; entries: typeof entries }>()
for (const e of entries) {
if (!projectMap.has(e.project.id)) {
projectMap.set(e.project.id, { id: e.project.id, name: e.project.name, entries: [] })
}
projectMap.get(e.project.id)!.entries.push(e)
}
// Build grid: per project, per day, sum minutes
const grid = Array.from(projectMap.values()).map((p) => {
const dayData = days.map((d) => {
const dayEntries = p.entries.filter((e) => {
const ed = new Date(e.date)
return ed.getDate() === d.getDate() && ed.getMonth() === d.getMonth() && ed.getFullYear() === d.getFullYear()
})
const minutes = dayEntries.reduce((s, e) => s + e.minutes, 0)
const locked = dayEntries.every((e) => e.locked)
return {
date: d.toISOString(),
minutes,
locked,
entries: dayEntries.map((e) => ({ id: e.id, description: e.description, minutes: e.minutes, billable: e.billable, locked: e.locked })),
}
})
const totalMinutes = dayData.reduce((s, d) => s + d.minutes, 0)
return {
project: { id: p.id, name: p.name },
days: dayData,
totalMinutes,
}
})
// Day totals
const dayTotals = days.map((d, i) => ({
date: d.toISOString(),
label: d.toLocaleDateString('de-DE', { weekday: 'short' }),
dayNum: d.getDate(),
minutes: grid.reduce((s, p) => s + p.days[i]!.minutes, 0),
}))
const grandTotalMinutes = grid.reduce((s, p) => s + p.totalMinutes, 0)
const billableMinutes = entries.filter((e) => e.billable).reduce((s, e) => s + e.minutes, 0)
const lockedCount = entries.filter((e) => e.locked).length
const allLocked = entries.length > 0 && entries.every((e) => e.locked)
const weekLabel = `${monday.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' })} – ${sunday.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' })}`
return NextResponse.json({
week: { start: monday.toISOString(), end: sunday.toISOString(), label: weekLabel },
grid,
dayTotals,
stats: {
grandTotalMinutes,
billableMinutes,
lockedCount,
totalEntries: entries.length,
allLocked,
projectCount: grid.length,
},
})
}
// PATCH /api/aos/timesheet — lock all entries in a week (PM approval)
export async function PATCH(req: Request) {
const { week } = (await req.json()) as { week?: string }
if (!week) return NextResponse.json({ error: 'week required (YYYY-MM-DD)' }, { status: 400 })
const refDate = new Date(week + 'T00:00:00Z')
if (isNaN(refDate.getTime())) return NextResponse.json({ error: 'invalid date' }, { status: 400 })
const day = refDate.getDay()
const offset = day === 0 ? -6 : 1 - day
const monday = new Date(refDate)
monday.setDate(refDate.getDate() + offset)
monday.setHours(0, 0, 0, 0)
const sunday = new Date(monday)
sunday.setDate(monday.getDate() + 6)
sunday.setHours(23, 59, 59, 999)
const result = await db.timeEntry.updateMany({
where: { date: { gte: monday, lte: sunday }, locked: false },
data: { locked: true },
})
await db.auditLog.create({
data: {
actor: 'aylin@nordlicht.digital',
action: 'timesheet.lock:week',
entity: 'TimeEntry',
entityId: 'batch',
after: JSON.stringify({ week: monday.toISOString().slice(0, 10), locked: result.count }),
hash: 'sha256:' + Math.random().toString(36).slice(2),
},
})
return NextResponse.json({ locked: result.count, week: monday.toISOString().slice(0, 10) })
}