From 0a3d21215cba1f003632ee04e1afeb19e0b6f23a Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 12:26:56 -0700 Subject: [PATCH] get service and usage --- app/main/views/organizations.py | 45 ++++++++++++++++++- app/models/organization.py | 4 +- app/notify_client/organizations_api_client.py | 4 +- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 84532d6b3..74a8a63cc 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -25,6 +25,7 @@ from app.main.views.dashboard import ( from app.models.organization import AllOrganizations, Organization from app.models.user import InvitedOrgUser, User from app.utils.csv import Spreadsheet +from app.utils.time import parse_naive_dt from app.utils.user import user_has_permissions, user_is_platform_admin from notifications_python_client.errors import HTTPError @@ -76,6 +77,46 @@ def get_organization_message_allowance(org_id): } +def get_services_usage(organization, year): + + try: + services_and_usage = organization.services_and_usage(financial_year=year, include_all_services=True)["services"] + except Exception as e: + current_app.logger.error(f"Error fetching services and usage: {e}") + return [] + + services = [] + for service in services_and_usage: + service["id"] = service.get("service_id") + service["name"] = service.get("service_name") + + emails_sent = service.get("emails_sent", 0) + sms_sent = service.get("sms_billable_units", 0) + sms_remainder = service.get("sms_remainder", 0) + sms_cost = service.get("sms_cost", 0) + + usage_parts = [] + if emails_sent > 0: + usage_parts.append(f"{emails_sent:,} emails") + if sms_sent > 0 or sms_remainder > 0: + if sms_cost > 0: + usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} remaining, ${sms_cost:,.2f})") + else: + usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} remaining)") + + service["usage"] = ", ".join(usage_parts) if usage_parts else "No usage" + + if "created_at" in service and isinstance(service["created_at"], str): + try: + service["created_at"] = parse_naive_dt(service["created_at"]) + except (ValueError, TypeError): + service["created_at"] = None + + services.append(service) + + return services + + @main.route("/organizations/", methods=["GET"]) @user_has_permissions() def organization_dashboard(org_id): @@ -86,10 +127,12 @@ def organization_dashboard(org_id): message_allowance = get_organization_message_allowance(org_id) + services_with_usage = get_services_usage(current_organization, year) + return render_template( "views/organizations/organization/index.html", selected_year=year, - services=current_organization.services, + services=services_with_usage, live_services=len(current_organization.live_services), trial_services=len(current_organization.trial_services), suspended_services=len(current_organization.suspended_services), diff --git a/app/models/organization.py b/app/models/organization.py index 33f5f3ec4..e7eb09f37 100644 --- a/app/models/organization.py +++ b/app/models/organization.py @@ -139,8 +139,8 @@ class Organization(JSONModel, SortByNameMixin): def associate_service(self, service_id): organizations_client.update_service_organization(service_id, self.id) - def services_and_usage(self, financial_year): - return organizations_client.get_services_and_usage(self.id, financial_year) + def services_and_usage(self, financial_year, include_all_services=False): + return organizations_client.get_services_and_usage(self.id, financial_year, include_all_services) class Organizations(SerialisedModelCollection): diff --git a/app/notify_client/organizations_api_client.py b/app/notify_client/organizations_api_client.py index e7cca58ef..105f6dce6 100644 --- a/app/notify_client/organizations_api_client.py +++ b/app/notify_client/organizations_api_client.py @@ -72,10 +72,10 @@ class OrganizationsClient(NotifyAdminAPIClient): def remove_user_from_organization(self, org_id, user_id): return self.delete(f"/organizations/{org_id}/users/{user_id}") - def get_services_and_usage(self, org_id, year): + def get_services_and_usage(self, org_id, year, include_all_services=False): return self.get( url=f"/organizations/{org_id}/services-with-usage", - params={"year": str(year)}, + params={"year": str(year), "include_all_services": str(include_all_services).lower()}, ) def get_organization_message_usage(self, org_id):