"""Report runs persister.""" from datetime import datetime, timezone from typing import Optional import uuid from sqlalchemy import ( and_, case, distinct, func, select, ) from sqlalchemy.orm import Session from collaborator.connectors import mysql from collaborator.constants import error from collaborator.constants.reports import ( FileFormat, ReportSource, ReportStatus, TriggerType, ) from collaborator.models.rds.collaborator import Collaborator from collaborator.models.rds.report import Report from collaborator.models.rds.report_persister import ReportPersister from collaborator.models.rds.report_run import ReportRun from collaborator.utils.error import OwsError class ReportRunPersister(object): """Report run persister class.""" @classmethod @mysql.db_session def create_report_run( cls, name: str, currency: str, collaborators: list, period_ids: str, period_name: str, migrated_to_abacus: bool, requestor_identity_uuid: str, notification_email: str, number_format: str, session: Session, ): """Create a report run with the given parameters. Assumes that the currency will always be the same for all reports in the run, which will not be true in future when runs can involve multiple vendors. Args: collaborators (list): List of collaborators involved in the report run. period_ids (str): Identifier for the period of the report run. period_name (str): Name of the period for the report run. name (str): Name of the report run. currency (str): Currency used in the report run. migrated_to_abacus (bool): Flag indicating if the report run is migrated to Abacus. requestor_identity_uuid (str): UUID of the requestor's identity. notification_email (str): Email address for notifications. number_format (str): Format for numbers in the report run. session (Session): Database session for the report run. Returns: None """ report_run_uuid = str(uuid.uuid4()) request_datetime = datetime.now(tz=timezone.utc) source = ReportSource.ABACUS if migrated_to_abacus else ReportSource.LEGACY report_run = ReportRun( name=name, uuid=report_run_uuid, period_ids=period_ids, period_name=period_name, file_format=FileFormat.XLS, requested_datetime=request_datetime, source=source, requestor_identity_uuid=requestor_identity_uuid, notification_email=notification_email, number_format=number_format, trigger_type=TriggerType.MANUAL, ) session.add(report_run) session.flush() reports = ReportPersister.create_reports( collaborators=collaborators, period_name=period_name, report_run_id=report_run.report_run_id, report_run_name=name, currency=currency, request_datetime=request_datetime, ) session.add_all(reports) session.commit() return report_run, reports @classmethod @mysql.db_session def get_report_runs_by_ids( cls, report_run_ids: list, session: Session, ) -> list[ReportRun]: """Get report runs by ID. Args: report_run_ids (list): List of report run IDs to retrieve. session (Session): Database session for the query. Returns: list[ReportRun]: List of ReportRun objects. """ return ( session.query(ReportRun) .filter(ReportRun.report_run_id.in_(report_run_ids)) .all() ) @classmethod @mysql.db_session def get_report_run_participations( cls, account_id: Optional[str], report_run_id: Optional[int], sort_key: Optional[str], sort_direction: Optional[str], limit: int, offset: int, session: Session, ): """Get report run participations. Args: account_id (Optional[str]): account ID to filter by report_run_id (Optional[int]): report run ID to filter by session (Session): DB session sort_key (Optional[str]): column to sort by sort_direction (Optional[str]): direction to sort by limit (int): number of records to return offset (int): number of records to skip Returns: Tuple: report runs, count """ report_join_clauses = [ Report.report_run_id == ReportRun.report_run_id, Report.deleted_datetime == None, # noqa: E711 ] collab_join_clauses = [ Collaborator.collaborator_id == Report.collaborator_id, ] # If an account_id is provided, we group by report_run_id and # filter by account_id. if account_id is not None: collab_join_clauses.append(Collaborator.vendor_id == account_id) if report_run_id: report_join_clauses.append(Report.report_run_id == report_run_id) count_columns = [func.count(distinct(ReportRun.report_run_id))] group_by = ReportRun.report_run_id # If a report_run_id is provided, we group by account_id and # filter by report_run_id. elif report_run_id is not None: report_join_clauses.append(Report.report_run_id == report_run_id) count_columns = [func.count(distinct(Collaborator.vendor_id))] group_by = Collaborator.vendor_id else: raise OwsError( message=error.ERROR_MESSAGE_MISSING_PARAMS, ) base_query = ( select( ReportRun.report_run_id, Collaborator.vendor_id, func.sum(Report.amount).label("amount"), func.max(Report.currency).label("currency"), func.count(Report.report_id).label("total_count"), _reports_status_count(ReportStatus.ERROR, "error_count"), _reports_status_count(ReportStatus.REQUESTED, "requested_count"), _reports_status_count(ReportStatus.GENERATED, "generated_count"), func.count(Report.transaction_id).label("transaction_count"), ) .join(Report, and_(*report_join_clauses)) .join(Collaborator, and_(*collab_join_clauses)) ) order = ( getattr(ReportRun, sort_key) if sort_key else ReportRun.requested_datetime ) order = order.asc() if sort_direction == "ASC" else order.desc() rows_query = base_query.group_by(group_by).offset(offset).order_by(order) if limit: rows_query = rows_query.limit(limit) report_runs = session.execute(rows_query).fetchall() count_query = base_query.with_only_columns(*count_columns) count = session.execute(count_query).scalars().one() return report_runs, count def _reports_status_count(status: str, label: str): return func.count(case({status: 1}, value=Report.status)).label(label)