"""TransferWise Transaction Persister. Handles doing CRUD operations on the transaferwise_transaction. """ from datetime import datetime, timezone from typing import List, Optional from sqlalchemy import asc, desc from sqlalchemy.orm.session import Session from collaborator.connectors import mysql from collaborator.constants import error from collaborator.models.rds.transferwise_batch import TransferwiseBatch from collaborator.models.rds.transferwise_transaction import TransferwiseTransaction from collaborator.utils import logging from collaborator.utils.error import OwsError from collaborator.utils.helpers import sanitize_data from collaborator.utils.typing import Sort _DEFAULT_SORT = Sort("created_date", "DESC") class TransferwiseTransactionPersister: """Handles high level operations for TransferWise transactions.""" @classmethod @mysql.db_session def bulk_get_for_transferwise_batch_ids( cls, transferwise_batch_ids: List[int], session: Session ) -> list: """Bulk get transactions based on a list of TransferWise batch ID. Args: transferwise_batch_ids (List[int]): IDs of the TransferWise batches session (sqlalchemy.orm.session.Session): Database session Returns: list: Transactions associated with the TransferWise batches. """ query = session.query(TransferwiseTransaction).filter( TransferwiseTransaction.transferwise_batch_id.in_(transferwise_batch_ids) ) result = query.all() return [item.to_dict() for item in result] @classmethod @mysql.db_session def get_for_transferwise_batch_id( cls, transferwise_batch_id: int, session: Session ) -> list: """Get transactions based on TransferWise batch ID. Args: transferwise_batch_id (int): ID of the TransferWise batch session (sqlalchemy.orm.session.Session): Database session Returns: list: Transactions associated with the TransferWise batch. """ result = cls.bulk_get_for_transferwise_batch_ids([transferwise_batch_id]) return result @classmethod @mysql.db_session def save_transactions(cls, transactions: list, session: Session) -> list: """Save transactions. Args: transactions (list): list of transactions session (sqlalchemy.orm.session.Session): database session. Returns: list: The created transactions. """ transferwise_transactions = [ TransferwiseTransaction(**transaction) for transaction in transactions ] session.add_all(transferwise_transactions) session.commit() return [item.to_dict() for item in transferwise_transactions] @classmethod @mysql.db_session def get_by_quote_ids_for_vendor_profile( cls, vendor_id: int, profile_id: int, quote_ids: List[str], session: Session ) -> list: """Get TransferWise transactions for quotes scoped to a vendor profile.""" if not quote_ids: return [] transactions = ( session.query(TransferwiseTransaction) .join( TransferwiseBatch, TransferwiseTransaction.transferwise_batch_id == TransferwiseBatch.transferwise_batch_id, ) .filter( TransferwiseBatch.vendor_id == vendor_id, TransferwiseTransaction.profile_id == profile_id, TransferwiseTransaction.quote_id.in_(quote_ids), ) .all() ) return [transaction.to_dict() for transaction in transactions] @classmethod @mysql.db_session def update_transaction_status( cls, transfer_status: str, transfer_id: int, occurred_at: datetime, session: Session, ) -> dict: """Update TransferWise transaction status. Args: transfer_status (str): TW Transaction status session (sqlalchemy.orm.session.Session): database session. transfer_id (int): TW Transaction ID Returns: dict: the updated transaction """ # "SELECT ... FOR UPDATE" ensures this row is locked for the # duration of the provided session. query = session.query(TransferwiseTransaction).with_for_update() query = query.filter_by(transfer_id=transfer_id) transaction = query.first() if not transaction: raise OwsError.not_found( code=error.ERROR_CODE_TRANSACTION_NOT_FOUND, message=error.ERROR_MESSAGE_TRANSACTION_NOT_FOUND, ) previous_transaction_state = transaction.to_dict() if ( transaction.status_updated_date and transaction.status_updated_date.replace(tzinfo=timezone.utc) >= occurred_at ): return previous_transaction_state query.update({"status": transfer_status, "status_updated_date": occurred_at}) session.refresh(transaction) updated_transaction = transaction.to_dict() logging.log_event( logging.LOG_EVENT_UPDATE, "transferwise_transaction", updated_transaction["id"], previous_transaction_state, updated_transaction, None, ) return updated_transaction @classmethod @mysql.db_session def get_by_transfer_id(cls, transfer_id: int, session: Session): """Get TransferWise transaction by transfer id. Args: session (sqlalchemy.orm.session.Session): database session. transfer_id (int): TW Transaction ID Returns: dict: the updated transaction """ # "SELECT ... FOR UPDATE" ensures this row is locked for the # duration of the provided session. query = session.query(TransferwiseTransaction).with_for_update() query = query.filter_by(transfer_id=transfer_id) transaction = query.first() if not transaction: raise OwsError.not_found( code=error.ERROR_CODE_TRANSACTION_NOT_FOUND, message=error.ERROR_MESSAGE_TRANSACTION_NOT_FOUND, ) return transaction.to_dict() @classmethod @mysql.db_session def get_collaborator_payments( cls, vendor_id: int, collaborator_id: Optional[int], payment_statuses: list, limit: int, offset: int, sort_key: Optional[str], sort_direction: Optional[str], session: Session, ): """Get TransferWise transactions based on a list of statuses. Args: collaborator_id (int): collaborator unique identifier payment_statuses (list): PENDING or CANCELLED limit (int): the limit of records for pagination offset (int): the offset (for pagination) sort_key (str): Key to sort by sort_direction (str): Direction to sort by (ASC or DESC) session (sqlalchemy.orm.session.Session): database session. Returns: Tuple: with transferwise payments and the total number of records """ sort = _DEFAULT_SORT if sort_key and sort_direction: sort = Sort(sort_key, sort_direction) filters = [TransferwiseBatch.vendor_id == vendor_id] if collaborator_id: filters.append(TransferwiseTransaction.collaborator_id == collaborator_id) if len(payment_statuses): filters.append((TransferwiseTransaction.status.in_(payment_statuses))) order_attr = getattr(TransferwiseTransaction, sort.key) order_direction = asc if sort.direction == "ASC" else desc query = ( session.query(TransferwiseTransaction) .join( TransferwiseBatch, ( TransferwiseTransaction.transferwise_batch_id == TransferwiseBatch.transferwise_batch_id ), ) .filter(*filters) .order_by(order_direction(order_attr)) ) total_records = query.count() if limit != 0: limited_query = query.limit(limit).offset(offset) else: limited_query = query.offset(offset) payments = limited_query.all() return [sanitize_data(payment.to_dict()) for payment in payments], total_records