export const dynamic = 'force-dynamic'; import { NextRequest, NextResponse } from 'next/server'; import { supabase } from '@/lib/supabase'; import { determineKRStatus, determineProjectStatus } from '@/lib/kr-utils'; import type { KR, Checkin } from '@/types/database'; import { ensureAuthenticated, getAuthContext, hasProjectAccess } from '@/lib/access-control'; export async function GET(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const { searchParams } = new URL(request.url); const krId = searchParams.get('kr_id'); const projectId = searchParams.get('project_id'); const userId = searchParams.get('user_id'); const limit = searchParams.get('limit'); let query = supabase .from('checkins') .select(` *, user:users(id, name, avatar_url), kr:krs(id, title, metric_name, unit) `) .order('created_at', { ascending: false }); if (!ctx!.isAdmin) { if (userId && userId !== ctx!.user.id) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } if (projectId && !hasProjectAccess(ctx!, projectId)) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } if (krId) { const { data: kr, error: krErr } = await supabase .from('krs') .select('id,project_id') .eq('id', krId) .maybeSingle(); if (krErr) return NextResponse.json({ error: krErr.message }, { status: 500 }); if (!kr) return NextResponse.json({ error: 'KR不存在' }, { status: 404 }); if (!hasProjectAccess(ctx!, kr.project_id)) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } } if (!projectId && !krId) { if (ctx!.accessibleProjectIds.length === 0) { return NextResponse.json([]); } query = query.in('project_id', ctx!.accessibleProjectIds); } } if (krId) query = query.eq('kr_id', krId); if (projectId) query = query.eq('project_id', projectId); if (userId) query = query.eq('user_id', userId); if (limit) query = query.limit(parseInt(limit)); const { data, error } = await query; if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json(data); } export async function POST(request: NextRequest) { const ctx = await getAuthContext(request); const auth = ensureAuthenticated(ctx); if (!auth.ok) { return NextResponse.json({ error: auth.error }, { status: auth.status }); } const body = await request.json(); const { kr_id, project_id, user_id, date, current_value, confidence, reason_code, note } = body; if (!kr_id || !project_id || current_value === undefined || confidence === undefined) { return NextResponse.json( { error: 'KR ID、项目ID、当前值和信心度为必填项' }, { status: 400 } ); } const { data: krData, error: krError } = await supabase .from('krs') .select('*') .eq('id', kr_id) .single(); if (krError || !krData) { return NextResponse.json({ error: 'KR不存在' }, { status: 404 }); } const kr = krData as KR; if (kr.project_id !== project_id) { return NextResponse.json({ error: '项目与 KR 不匹配' }, { status: 400 }); } if (!ctx!.isAdmin) { if (!hasProjectAccess(ctx!, project_id)) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } if (user_id && user_id !== ctx!.user.id) { return NextResponse.json({ error: '无权限' }, { status: 403 }); } } const { data: recentCheckins } = await supabase .from('checkins') .select('*') .eq('kr_id', kr_id) .order('created_at', { ascending: false }) .limit(2); const delta_value = current_value - kr.current_value; const updatedKR: KR = { ...kr, current_value, confidence, reason_code: reason_code || kr.reason_code, note: note || kr.note, }; const newStatus = determineKRStatus(updatedKR, (recentCheckins as Checkin[]) || []); if ((newStatus === 'yellow' || newStatus === 'red') && !reason_code) { return NextResponse.json( { error: '黄色或红色状态必须提供原因代码' }, { status: 400 } ); } const checkinDate = date || new Date().toISOString().split('T')[0]; const { data: checkin, error: checkinError } = await supabase .from('checkins') .insert({ kr_id, project_id, user_id: ctx!.isAdmin ? user_id : ctx!.user.id, date: checkinDate, current_value, delta_value, confidence, status_snapshot: newStatus, reason_code, note, }) .select() .single(); if (checkinError) { return NextResponse.json({ error: checkinError.message }, { status: 500 }); } await supabase .from('krs') .update({ current_value, confidence, status: newStatus, reason_code: reason_code || (newStatus === 'green' ? null : kr.reason_code), note: note || kr.note, updated_at: new Date().toISOString(), }) .eq('id', kr_id); const { data: projectKRs } = await supabase .from('krs') .select('status') .eq('project_id', project_id); if (projectKRs) { const projectStatus = determineProjectStatus(projectKRs as KR[]); await supabase .from('projects') .update({ status: projectStatus, updated_at: new Date().toISOString() }) .eq('id', project_id); } return NextResponse.json(checkin, { status: 201 }); }