// 数据一致性校验与修复 Edge Function // 定期检查并修复 completed_quantity 与 inventory_records 不一致的问题 import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; Deno.serve(async (req) => { // CORS 预检 if (req.method === 'OPTIONS') { return new Response('ok', { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }, }); } const corsHeaders = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }; try { const body = await req.json(); const { action, planId } = body; // action: 'check' | 'fix' | 'check-all' // 获取认证信息 const authHeader = req.headers.get('Authorization') || ''; // 创建管理员客户端(绕过 RLS) const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, ); // 检查单个计划的数据一致性 const checkPlanConsistency = async (pid: string) => { // 获取计划信息 const { data: plan } = await supabaseAdmin .from('production_plans') .select('id, plan_code, completed_quantity, target_quantity') .eq('id', pid) .maybeSingle(); if (!plan) return null; // 计算入库记录总和 const { data: records } = await supabaseAdmin .from('inventory_records') .select('quantity') .eq('plan_id', pid); const inventoryTotal = (records || []).reduce((sum, r) => sum + (r.quantity || 0), 0); const completedQuantity = plan.completed_quantity || 0; const diff = inventoryTotal - completedQuantity; return { planId: pid, planCode: plan.plan_code, completedQuantity, inventoryTotal, diff, isConsistent: diff === 0, }; }; // 检查所有计划的数据一致性 const checkAllPlans = async () => { const { data: plans } = await supabaseAdmin .from('production_plans') .select('id'); const results = []; for (const plan of (plans || [])) { const result = await checkPlanConsistency(plan.id); if (result && !result.isConsistent) { results.push(result); } } return results; }; // 修复不一致的数据 const fixInconsistency = async (pid: string) => { const check = await checkPlanConsistency(pid); if (!check || check.isConsistent) { return { fixed: false, message: '数据已一致,无需修复' }; } // 以 inventory_records 为准修复 completed_quantity const { data: records } = await supabaseAdmin .from('inventory_records') .select('quantity') .eq('plan_id', pid); const inventoryTotal = (records || []).reduce((sum, r) => sum + (r.quantity || 0), 0); const { error } = await supabaseAdmin .from('production_plans') .update({ completed_quantity: inventoryTotal }) .eq('id', pid); if (error) { return { fixed: false, error: error.message }; } return { fixed: true, planId: pid, oldValue: check.completedQuantity, newValue: inventoryTotal, diff: check.diff, }; }; let result; switch (action) { case 'check': if (!planId) { return new Response( JSON.stringify({ error: 'check 操作需要提供 planId' }), { status: 400, headers: corsHeaders } ); } result = await checkPlanConsistency(planId); break; case 'check-all': result = await checkAllPlans(); break; case 'fix': if (!planId) { return new Response( JSON.stringify({ error: 'fix 操作需要提供 planId' }), { status: 400, headers: corsHeaders } ); } result = await fixInconsistency(planId); break; case 'fix-all': const inconsistentPlans = await checkAllPlans(); const fixResults = []; for (const plan of inconsistentPlans) { const fixResult = await fixInconsistency(plan.planId); fixResults.push(fixResult); } result = { totalChecked: inconsistentPlans.length, fixed: fixResults.filter(r => r.fixed).length, details: fixResults, }; break; default: return new Response( JSON.stringify({ error: '无效的操作类型,支持: check, check-all, fix, fix-all' }), { status: 400, headers: corsHeaders } ); } return new Response( JSON.stringify({ success: true, data: result }), { headers: corsHeaders } ); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; return new Response( JSON.stringify({ error: '服务器错误: ' + message }), { status: 500, headers: corsHeaders } ); } });