diff --git a/app/modules/sag/backend/router.py b/app/modules/sag/backend/router.py index 9471ebb..e473006 100644 --- a/app/modules/sag/backend/router.py +++ b/app/modules/sag/backend/router.py @@ -3342,50 +3342,120 @@ async def add_sag_email_link(sag_id: int, payload: dict): @router.get("/sag/{sag_id}/email-links") async def get_sag_emails(sag_id: int): """Get emails linked to a case.""" - query = """ - WITH linked_emails AS ( + if not _table_exists("sag_emails") or not _table_exists("email_messages"): + logger.warning("⚠️ Email links requested for SAG-%s but required tables are missing", sag_id) + return [] + + has_thread_key = table_has_column("email_messages", "thread_key") + has_email_references = table_has_column("email_messages", "email_references") + has_in_reply_to = table_has_column("email_messages", "in_reply_to") + has_subject = table_has_column("email_messages", "subject") + has_message_id = table_has_column("email_messages", "message_id") + has_folder = table_has_column("email_messages", "folder") + has_status = table_has_column("email_messages", "status") + has_received_date = table_has_column("email_messages", "received_date") + + thread_key_expr = "e.thread_key" if has_thread_key else "NULL" + email_references_expr = "e.email_references" if has_email_references else "NULL" + in_reply_to_expr = "e.in_reply_to" if has_in_reply_to else "NULL" + subject_expr = "e.subject" if has_subject else "NULL" + message_id_expr = "e.message_id" if has_message_id else "NULL" + + outgoing_checks = [] + if has_folder: + outgoing_checks.append("LOWER(COALESCE(linked_emails.folder, '')) LIKE 'sent%%'") + if has_status: + outgoing_checks.append("LOWER(COALESCE(linked_emails.status, '')) = 'sent'") + is_outgoing_expr = " OR ".join(outgoing_checks) if outgoing_checks else "FALSE" + + if has_received_date: + query = f""" + WITH linked_emails AS ( + SELECT + e.*, + COALESCE( + NULLIF(REGEXP_REPLACE(TRIM(COALESCE({thread_key_expr}, '')), '[<>\\s]', '', 'g'), ''), + NULLIF(REGEXP_REPLACE((REGEXP_SPLIT_TO_ARRAY(COALESCE({email_references_expr}, ''), E'[\\s,]+'))[1], '[<>\\s]', '', 'g'), ''), + NULLIF( + REGEXP_REPLACE( + (REGEXP_SPLIT_TO_ARRAY(COALESCE({in_reply_to_expr}, ''), E'[\\s,]+'))[1], + '[<>\\s]', + '', + 'g' + ), + '' + ), + NULLIF( + REGEXP_REPLACE( + LOWER(TRIM(COALESCE({subject_expr}, ''))), + '^(?:(?:re|fw|fwd|sv|aw)\\s*:\\s*)+', + '', + 'i' + ), + '' + ), + NULLIF(REGEXP_REPLACE(TRIM(COALESCE({message_id_expr}, '')), '[<>\\s]', '', 'g'), ''), + CONCAT('email-', e.id::text) + ) AS resolved_thread_key + FROM email_messages e + JOIN sag_emails se ON e.id = se.email_id + WHERE se.sag_id = %s + ) SELECT - e.*, - COALESCE( - NULLIF(REGEXP_REPLACE(TRIM(COALESCE(e.thread_key, '')), '[<>\\s]', '', 'g'), ''), - NULLIF(REGEXP_REPLACE((REGEXP_SPLIT_TO_ARRAY(COALESCE(e.email_references, ''), E'[\\s,]+'))[1], '[<>\\s]', '', 'g'), ''), - NULLIF( - REGEXP_REPLACE( - (REGEXP_SPLIT_TO_ARRAY(COALESCE(e.in_reply_to, ''), E'[\\s,]+'))[1], - '[<>\\s]', - '', - 'g' + linked_emails.*, + ({is_outgoing_expr}) AS is_outgoing, + COUNT(*) OVER (PARTITION BY linked_emails.resolved_thread_key) AS thread_message_count, + MAX(linked_emails.received_date) OVER (PARTITION BY linked_emails.resolved_thread_key) AS thread_last_received_date + FROM linked_emails + ORDER BY thread_last_received_date DESC NULLS LAST, received_date DESC + """ + else: + query = f""" + WITH linked_emails AS ( + SELECT + e.*, + COALESCE( + NULLIF(REGEXP_REPLACE(TRIM(COALESCE({thread_key_expr}, '')), '[<>\\s]', '', 'g'), ''), + NULLIF(REGEXP_REPLACE((REGEXP_SPLIT_TO_ARRAY(COALESCE({email_references_expr}, ''), E'[\\s,]+'))[1], '[<>\\s]', '', 'g'), ''), + NULLIF( + REGEXP_REPLACE( + (REGEXP_SPLIT_TO_ARRAY(COALESCE({in_reply_to_expr}, ''), E'[\\s,]+'))[1], + '[<>\\s]', + '', + 'g' + ), + '' ), - '' - ), - NULLIF( - REGEXP_REPLACE( - LOWER(TRIM(COALESCE(e.subject, ''))), - '^(?:(?:re|fw|fwd|sv|aw)\\s*:\\s*)+', - '', - 'i' + NULLIF( + REGEXP_REPLACE( + LOWER(TRIM(COALESCE({subject_expr}, ''))), + '^(?:(?:re|fw|fwd|sv|aw)\\s*:\\s*)+', + '', + 'i' + ), + '' ), - '' - ), - NULLIF(REGEXP_REPLACE(TRIM(COALESCE(e.message_id, '')), '[<>\\s]', '', 'g'), ''), - CONCAT('email-', e.id::text) - ) AS resolved_thread_key - FROM email_messages e - JOIN sag_emails se ON e.id = se.email_id - WHERE se.sag_id = %s - ) - SELECT - linked_emails.*, - ( - LOWER(COALESCE(linked_emails.folder, '')) LIKE 'sent%%' - OR LOWER(COALESCE(linked_emails.status, '')) = 'sent' - ) AS is_outgoing, - COUNT(*) OVER (PARTITION BY linked_emails.resolved_thread_key) AS thread_message_count, - MAX(linked_emails.received_date) OVER (PARTITION BY linked_emails.resolved_thread_key) AS thread_last_received_date - FROM linked_emails - ORDER BY thread_last_received_date DESC NULLS LAST, received_date DESC - """ - return execute_query(query, (sag_id,)) or [] + NULLIF(REGEXP_REPLACE(TRIM(COALESCE({message_id_expr}, '')), '[<>\\s]', '', 'g'), ''), + CONCAT('email-', e.id::text) + ) AS resolved_thread_key + FROM email_messages e + JOIN sag_emails se ON e.id = se.email_id + WHERE se.sag_id = %s + ) + SELECT + linked_emails.*, + ({is_outgoing_expr}) AS is_outgoing, + COUNT(*) OVER (PARTITION BY linked_emails.resolved_thread_key) AS thread_message_count, + NULL::timestamp AS thread_last_received_date + FROM linked_emails + ORDER BY linked_emails.id DESC + """ + + try: + return execute_query(query, (sag_id,)) or [] + except Exception as exc: + logger.error("❌ Failed loading linked emails for SAG-%s: %s", sag_id, exc) + return [] @router.delete("/sag/{sag_id}/email-links/{email_id}") async def remove_sag_email_link(sag_id: int, email_id: int): diff --git a/app/modules/sag/templates/detail.html b/app/modules/sag/templates/detail.html index f2a7df0..a89034e 100644 --- a/app/modules/sag/templates/detail.html +++ b/app/modules/sag/templates/detail.html @@ -2497,6 +2497,33 @@ + +