177 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 数据一致性校验与修复 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 }
);
}
});