diff --git a/app/modules/bottom_bar/backend/router.py b/app/modules/bottom_bar/backend/router.py
index 98f5dcf..6886d5b 100644
--- a/app/modules/bottom_bar/backend/router.py
+++ b/app/modules/bottom_bar/backend/router.py
@@ -655,7 +655,7 @@ def _has_boss_access(current_user: dict) -> bool:
(int(current_user_id),),
) or []
names = [str(r.get("name") or "") for r in rows]
- tokens = ("admin", "manager", "leder", "chef", "teknik", "technician", "support")
+ tokens = ("admin", "manager", "leder", "chef")
return any(any(token in name for token in tokens) for name in names)
diff --git a/app/modules/bottom_bar/backend/service.py b/app/modules/bottom_bar/backend/service.py
index 6c36099..19de73b 100644
--- a/app/modules/bottom_bar/backend/service.py
+++ b/app/modules/bottom_bar/backend/service.py
@@ -145,12 +145,16 @@ def get_user_messages_summary(user_id: Optional[int], limit: int = 20) -> Dict[s
m.message_text,
m.requires_manual_ack,
m.created_at,
- m.read_at,
+ receipt.read_at,
+ receipt.acknowledged_at,
COALESCE(NULLIF(sender.full_name, ''), sender.username, ('Bruger #' || sender.user_id::text)) AS sender_name,
COALESCE(NULLIF(recipient.full_name, ''), recipient.username, ('Bruger #' || recipient.user_id::text)) AS recipient_name
FROM bottom_bar_messages m
JOIN users sender ON sender.user_id = m.sender_user_id
LEFT JOIN users recipient ON recipient.user_id = m.recipient_user_id
+ LEFT JOIN bottom_bar_message_receipts receipt
+ ON receipt.message_id = m.id
+ AND receipt.user_id = %s
WHERE m.sender_user_id = %s
OR m.recipient_user_id = %s
OR (m.recipient_user_id IS NULL AND EXISTS (
@@ -162,21 +166,24 @@ def get_user_messages_summary(user_id: Optional[int], limit: int = 20) -> Dict[s
ORDER BY m.created_at DESC, m.id DESC
LIMIT %s
""",
- (int(user_id), int(user_id), int(user_id), safe_limit),
+ (int(user_id), int(user_id), int(user_id), int(user_id), safe_limit),
) or []
unread_row = execute_query_single(
"""
SELECT COUNT(*) AS count
- FROM bottom_bar_messages
- WHERE read_at IS NULL
- AND sender_user_id <> %s
+ FROM bottom_bar_messages m
+ LEFT JOIN bottom_bar_message_receipts receipt
+ ON receipt.message_id = m.id
+ AND receipt.user_id = %s
+ WHERE receipt.read_at IS NULL
+ AND m.sender_user_id <> %s
AND (
- recipient_user_id = %s
- OR recipient_user_id IS NULL
+ m.recipient_user_id = %s
+ OR m.recipient_user_id IS NULL
)
""",
- (int(user_id), int(user_id)),
+ (int(user_id), int(user_id), int(user_id)),
) or {}
unread_count = _safe_count(unread_row)
@@ -197,7 +204,7 @@ def get_user_messages_summary(user_id: Optional[int], limit: int = 20) -> Dict[s
"created_at": row.get("created_at").isoformat() if row.get("created_at") else None,
"is_own": int(row.get("sender_user_id") or 0) == int(user_id),
"is_unread": row.get("read_at") is None and int(row.get("sender_user_id") or 0) != int(user_id),
- "is_acknowledged": row.get("read_at") is not None,
+ "is_acknowledged": row.get("acknowledged_at") is not None,
}
)
@@ -215,31 +222,40 @@ def mark_user_messages_read(user_id: Optional[int], partner_user_id: Optional[in
params: List[Any] = [int(user_id)]
if partner_user_id is not None and int(partner_user_id) > 0:
where_clause = """
- recipient_user_id = %s
- AND read_at IS NULL
- AND COALESCE(requires_manual_ack, FALSE) = FALSE
- AND sender_user_id <> %s
+ (m.recipient_user_id = %s OR m.recipient_user_id IS NULL)
+ AND COALESCE(m.requires_manual_ack, FALSE) = FALSE
+ AND m.sender_user_id <> %s
"""
params.append(int(user_id))
- where_clause += " AND sender_user_id = %s"
+ where_clause += " AND m.sender_user_id = %s"
params.append(int(partner_user_id))
else:
where_clause = """
- (recipient_user_id = %s OR recipient_user_id IS NULL)
- AND read_at IS NULL
- AND COALESCE(requires_manual_ack, FALSE) = FALSE
- AND sender_user_id <> %s
+ (m.recipient_user_id = %s OR m.recipient_user_id IS NULL)
+ AND COALESCE(m.requires_manual_ack, FALSE) = FALSE
+ AND m.sender_user_id <> %s
"""
params.append(int(user_id))
row = execute_query_single(
f"""
- UPDATE bottom_bar_messages
- SET read_at = CURRENT_TIMESTAMP
- WHERE {where_clause}
- RETURNING COUNT(*) OVER() AS affected_count
+ WITH eligible AS (
+ SELECT m.id
+ FROM bottom_bar_messages m
+ LEFT JOIN bottom_bar_message_receipts r
+ ON r.message_id = m.id AND r.user_id = %s
+ WHERE {where_clause}
+ AND r.read_at IS NULL
+ ), updated AS (
+ INSERT INTO bottom_bar_message_receipts (message_id, user_id, read_at)
+ SELECT id, %s, CURRENT_TIMESTAMP FROM eligible
+ ON CONFLICT (message_id, user_id) DO UPDATE
+ SET read_at = COALESCE(bottom_bar_message_receipts.read_at, EXCLUDED.read_at)
+ RETURNING message_id
+ )
+ SELECT COUNT(*) AS affected_count FROM updated
""",
- tuple(params),
+ tuple([int(user_id)] + params + [int(user_id)]),
) or {}
return int(row.get("affected_count") or 0)
@@ -252,14 +268,18 @@ def acknowledge_message(user_id: Optional[int], message_id: int) -> bool:
ensure_bottom_bar_messages_schema()
row = execute_query_single(
"""
- UPDATE bottom_bar_messages
- SET read_at = CURRENT_TIMESTAMP
- WHERE id = %s
- AND recipient_user_id = %s
- AND read_at IS NULL
- RETURNING id
+ INSERT INTO bottom_bar_message_receipts (message_id, user_id, read_at, acknowledged_at)
+ SELECT m.id, %s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+ FROM bottom_bar_messages m
+ WHERE m.id = %s
+ AND (m.recipient_user_id = %s OR m.recipient_user_id IS NULL)
+ AND m.sender_user_id <> %s
+ ON CONFLICT (message_id, user_id) DO UPDATE
+ SET read_at = COALESCE(bottom_bar_message_receipts.read_at, EXCLUDED.read_at),
+ acknowledged_at = COALESCE(bottom_bar_message_receipts.acknowledged_at, EXCLUDED.acknowledged_at)
+ RETURNING message_id AS id
""",
- (int(message_id), int(user_id)),
+ (int(user_id), int(message_id), int(user_id), int(user_id)),
) or {}
return bool(row.get("id"))
@@ -333,19 +353,13 @@ def _can_view_boss_tab(user_id: Optional[int]) -> bool:
group_names = _get_user_group_names(user_id)
if not group_names:
- # Fail-open for authenticated users if group mapping is missing.
- return True
+ return False
leadership_tokens = (
"admin",
"manager",
"leder",
"chef",
- "teknik",
- "technician",
- "support",
- "drift",
- "it",
)
return any(
any(token in group for token in leadership_tokens)
diff --git a/app/modules/sag/frontend/views.py b/app/modules/sag/frontend/views.py
index 642b326..1cfb638 100644
--- a/app/modules/sag/frontend/views.py
+++ b/app/modules/sag/frontend/views.py
@@ -289,8 +289,10 @@ async def sager_liste(
placeholders = ", ".join(["%s"] * len(closed_statuses))
query += f" AND LOWER(COALESCE(s.status, '')) NOT IN ({placeholders})"
params.extend(closed_statuses)
- if normalized_priority:
- query += " AND LOWER(COALESCE(s.priority, 'normal')) = %s"
+ if normalized_priority == "urgent":
+ query += " AND LOWER(COALESCE(s.priority::text, 'normal')) IN ('urgent', 'high', 'kritisk', 'critical')"
+ elif normalized_priority:
+ query += " AND LOWER(COALESCE(s.priority::text, 'normal')) = %s"
params.append(normalized_priority)
if customer_id_int:
query += " AND s.customer_id = %s"
@@ -355,8 +357,10 @@ async def sager_liste(
placeholders = ", ".join(["%s"] * len(closed_statuses))
fallback_query += f" AND LOWER(COALESCE(s.status, '')) NOT IN ({placeholders})"
fallback_params.extend(closed_statuses)
- if normalized_priority:
- fallback_query += " AND LOWER(COALESCE(s.priority, 'normal')) = %s"
+ if normalized_priority == "urgent":
+ fallback_query += " AND LOWER(COALESCE(s.priority::text, 'normal')) IN ('urgent', 'high', 'kritisk', 'critical')"
+ elif normalized_priority:
+ fallback_query += " AND LOWER(COALESCE(s.priority::text, 'normal')) = %s"
fallback_params.append(normalized_priority)
if customer_id_int:
fallback_query += " AND s.customer_id = %s"
diff --git a/app/shared/frontend/base.html b/app/shared/frontend/base.html
index 3889fe7..70d7edc 100644
--- a/app/shared/frontend/base.html
+++ b/app/shared/frontend/base.html
@@ -1578,7 +1578,7 @@ if (bmcOriginalFetch) {
-
+