mirror of
https://github.com/GSA/notifications-api.git
synced 2026-05-07 01:28:26 -04:00
Add organization message totals endpoint
- Add GET /organizations/<org_id>/message-totals endpoint - Returns aggregated messages_sent, messages_remaining, and total_message_limit - Add dao_get_notification_counts_for_organization() for bulk queries
This commit is contained in:
@@ -340,6 +340,67 @@ def dao_get_notification_count_for_service_message_ratio(service_id, current_yea
|
||||
return recent_count + old_count
|
||||
|
||||
|
||||
def dao_get_notification_counts_for_organization(service_ids, current_year):
|
||||
"""
|
||||
Get notification counts for multiple services in a single organization.
|
||||
"""
|
||||
if not service_ids:
|
||||
return {}
|
||||
|
||||
start_date = datetime(current_year, 6, 16)
|
||||
end_date = datetime(current_year + 1, 6, 16)
|
||||
|
||||
stmt1 = (
|
||||
select(
|
||||
Notification.service_id,
|
||||
func.count().label("count")
|
||||
)
|
||||
.where(
|
||||
Notification.service_id.in_(service_ids),
|
||||
Notification.status
|
||||
not in [
|
||||
NotificationStatus.CANCELLED,
|
||||
NotificationStatus.CREATED,
|
||||
NotificationStatus.SENDING,
|
||||
],
|
||||
Notification.created_at >= start_date,
|
||||
Notification.created_at < end_date,
|
||||
)
|
||||
.group_by(Notification.service_id)
|
||||
)
|
||||
|
||||
stmt2 = (
|
||||
select(
|
||||
NotificationHistory.service_id,
|
||||
func.count().label("count")
|
||||
)
|
||||
.where(
|
||||
NotificationHistory.service_id.in_(service_ids),
|
||||
NotificationHistory.status
|
||||
not in [
|
||||
NotificationStatus.CANCELLED,
|
||||
NotificationStatus.CREATED,
|
||||
NotificationStatus.SENDING,
|
||||
],
|
||||
NotificationHistory.created_at >= start_date,
|
||||
NotificationHistory.created_at < end_date,
|
||||
)
|
||||
.group_by(NotificationHistory.service_id)
|
||||
)
|
||||
|
||||
result_dict = {}
|
||||
|
||||
recent_results = db.session.execute(stmt1).all()
|
||||
for service_id, count in recent_results:
|
||||
result_dict[service_id] = count
|
||||
|
||||
history_results = db.session.execute(stmt2).all()
|
||||
for service_id, count in history_results:
|
||||
result_dict[service_id] = result_dict.get(service_id, 0) + count
|
||||
|
||||
return result_dict
|
||||
|
||||
|
||||
def dao_get_failed_notification_count():
|
||||
stmt = select(func.count(Notification.id)).where(
|
||||
Notification.status == NotificationStatus.FAILED
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from flask import Blueprint, abort, current_app, jsonify, request
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -8,6 +10,7 @@ from app.config import QueueNames
|
||||
from app.dao.annual_billing_dao import set_default_free_allowance_for_service
|
||||
from app.dao.dao_utils import transaction
|
||||
from app.dao.fact_billing_dao import fetch_usage_year_for_organization
|
||||
from app.dao.notifications_dao import dao_get_notification_counts_for_organization
|
||||
from app.dao.organization_dao import (
|
||||
dao_add_service_to_organization,
|
||||
dao_add_user_to_organization,
|
||||
@@ -259,3 +262,37 @@ def send_notifications_on_mou_signed(organization_id):
|
||||
organization.agreement_signed_by.email_address,
|
||||
personalisation,
|
||||
)
|
||||
|
||||
|
||||
@organization_blueprint.route("/<uuid:organization_id>/message-totals", methods=["GET"])
|
||||
def get_organization_message_totals(organization_id):
|
||||
|
||||
check_suspicious_id(organization_id)
|
||||
|
||||
dao_get_organization_by_id(organization_id)
|
||||
|
||||
services = dao_get_organization_services(organization_id)
|
||||
|
||||
if not services:
|
||||
return jsonify({
|
||||
"messages_sent": 0,
|
||||
"messages_remaining": 0,
|
||||
"total_message_limit": 0,
|
||||
}), 200
|
||||
|
||||
current_year = datetime.now(tz=ZoneInfo("UTC")).year
|
||||
service_ids = [service.id for service in services]
|
||||
|
||||
messages_by_service = dao_get_notification_counts_for_organization(
|
||||
service_ids, current_year
|
||||
)
|
||||
|
||||
total_messages_sent = sum(messages_by_service.get(s.id, 0) for s in services)
|
||||
total_message_limit = sum(s.total_message_limit for s in services)
|
||||
total_messages_remaining = total_message_limit - total_messages_sent
|
||||
|
||||
return jsonify({
|
||||
"messages_sent": total_messages_sent,
|
||||
"messages_remaining": total_messages_remaining,
|
||||
"total_message_limit": total_message_limit,
|
||||
}), 200
|
||||
|
||||
@@ -928,3 +928,57 @@ def test_missing_both():
|
||||
{"org_id": ["Can't be empty"]},
|
||||
{"name": ["Can't be empty"]},
|
||||
]
|
||||
|
||||
|
||||
@freeze_time("2025-01-15 10:00:00")
|
||||
def test_get_organization_message_totals(admin_request, sample_organization, mocker):
|
||||
service_1 = create_service(service_name="Service 1")
|
||||
service_2 = create_service(service_name="Service 2")
|
||||
|
||||
service_1.total_message_limit = 100000
|
||||
service_2.total_message_limit = 50000
|
||||
|
||||
dao_add_service_to_organization(service_1, sample_organization.id)
|
||||
dao_add_service_to_organization(service_2, sample_organization.id)
|
||||
|
||||
mock_get_counts = mocker.patch(
|
||||
"app.organization.rest.dao_get_notification_counts_for_organization"
|
||||
)
|
||||
mock_get_counts.return_value = {
|
||||
service_1.id: 30000,
|
||||
service_2.id: 20000,
|
||||
}
|
||||
|
||||
response = admin_request.get(
|
||||
"organization.get_organization_message_totals",
|
||||
organization_id=sample_organization.id,
|
||||
_expected_status=200,
|
||||
)
|
||||
|
||||
assert response["messages_sent"] == 50000
|
||||
assert response["messages_remaining"] == 100000
|
||||
assert response["total_message_limit"] == 150000
|
||||
|
||||
assert mock_get_counts.call_count == 1
|
||||
mock_get_counts.assert_called_once_with([service_1.id, service_2.id], 2025)
|
||||
|
||||
|
||||
def test_get_organization_message_totals_no_services(admin_request, sample_organization):
|
||||
response = admin_request.get(
|
||||
"organization.get_organization_message_totals",
|
||||
organization_id=sample_organization.id,
|
||||
_expected_status=200,
|
||||
)
|
||||
|
||||
assert response["messages_sent"] == 0
|
||||
assert response["messages_remaining"] == 0
|
||||
assert response["total_message_limit"] == 0
|
||||
|
||||
|
||||
def test_get_organization_message_totals_invalid_org_id(admin_request):
|
||||
fake_uuid = "00000000-0000-0000-0000-000000000000"
|
||||
admin_request.get(
|
||||
"organization.get_organization_message_totals",
|
||||
organization_id=fake_uuid,
|
||||
_expected_status=404,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user