bmc_hub/migrations/1047_telefoni_reused_callids.sql

62 lines
2.7 KiB
MySQL
Raw Normal View History

2026-09-25 11:33:53 +02:00
-- Yealink/PBX call IDs are reused after a phone or PBX lifecycle.
-- Keep historical calls and require uniqueness only while a call is active.
DROP INDEX IF EXISTS ux_telefoni_opkald_callid;
CREATE UNIQUE INDEX IF NOT EXISTS ux_telefoni_opkald_active_callid
ON telefoni_opkald(callid)
WHERE ended_at IS NULL;
-- Recover calls that were previously merged into an older row with the same
-- reused call ID. System events are written from the successful callback and
-- retain the callback metadata required for a read-safe reconstruction.
INSERT INTO telefoni_opkald (
callid, bruger_id, direction, ekstern_nummer, intern_extension,
started_at, ended_at, duration_sec, raw_payload,
lifecycle_status, termination_source, last_event_at
)
SELECT
established.correlation_id,
established.user_id,
COALESCE(NULLIF(established.details->'payload'->>'direction', ''), 'inbound'),
NULLIF(established.details->>'external_number', ''),
NULLIF(established.details->>'extension', ''),
established.occurred_at AT TIME ZONE 'UTC',
terminated.occurred_at AT TIME ZONE 'UTC',
CASE
WHEN NULLIF(terminated.details->>'duration_sec', '') ~ '^[0-9]+$'
THEN (terminated.details->>'duration_sec')::INTEGER
WHEN terminated.occurred_at IS NOT NULL
THEN GREATEST(EXTRACT(EPOCH FROM (terminated.occurred_at - established.occurred_at))::INTEGER, 0)
ELSE NULL
END,
COALESCE(established.details->'payload', '{}'::jsonb),
CASE WHEN terminated.id IS NULL THEN 'active' ELSE 'completed' END,
CASE WHEN terminated.id IS NULL THEN NULL ELSE 'system_log_recovery' END,
COALESCE(terminated.occurred_at, established.occurred_at)
FROM system_event_log established
LEFT JOIN LATERAL (
SELECT term.id, term.occurred_at, term.details
FROM system_event_log term
WHERE term.source = 'voip'
AND term.event_type = 'voip.terminated'
AND term.correlation_id = established.correlation_id
AND term.occurred_at >= established.occurred_at
ORDER BY term.occurred_at ASC, term.id ASC
LIMIT 1
) terminated ON TRUE
WHERE established.source = 'voip'
AND established.event_type = 'voip.established'
AND NULLIF(TRIM(established.correlation_id), '') IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM telefoni_opkald current_call
WHERE current_call.callid = established.correlation_id
AND current_call.started_at BETWEEN
(established.occurred_at AT TIME ZONE 'UTC') - INTERVAL '5 minutes'
AND (established.occurred_at AT TIME ZONE 'UTC') + INTERVAL '5 minutes'
);
CREATE INDEX IF NOT EXISTS idx_telefoni_opkald_callid_started
ON telefoni_opkald(callid, started_at DESC);