"""Abacus event model. Model for managing abacus events. """ from abacus_common_logic.connectors.database import db from abacus_common_logic.models import CRUDMixin, NormalizedDateTime from abacus_common_logic.utils.dates import current_timestamp from abacus_common_logic.utils.users import get_flask_user_id from sqlalchemy import func, schema, select, update class AbacusEvent(CRUDMixin, db.Model): """Abacus Event model. If a statement_period_id is not specified on creation, it is automatically set to the current OR earliest open statement_period by a database trigger (`before_insert_abacus_event`). """ __tablename__ = 'abacus_event' __mapper_args__ = {'eager_defaults': True} # Issue a SELECT after INSERT if needed abacus_event_id = db.Column(db.Integer, primary_key=True) event_date = db.Column( NormalizedDateTime(fsp=6), nullable=False, default=current_timestamp ) event_name = db.Column(db.String(32), nullable=False) previous_abacus_event_id = db.Column(db.Integer, nullable=True) rolled_back_at = db.Column(NormalizedDateTime(), nullable=True) statement_period_id = db.Column( db.Integer, nullable=False, server_default=schema.FetchedValue(), # Value is set by DB trigger ) target_id = db.Column(db.Integer, nullable=False) target_type = db.Column(db.String(32), nullable=False) created_by = db.Column(db.String(180), nullable=False) def __init__(self, *args, **kwargs): """Initialize model with default values.""" self.created_by = get_flask_user_id() super().__init__(*args, **kwargs) @classmethod def active_events(cls): """Get select statement for non-rolled-back events.""" return select(cls).where(cls.rolled_back_at.is_(None)) @classmethod def get_payment_approval_count(cls, payment_group_payment_id): """Return approval count of payment_group_payment events by target_id.""" stmt = ( select( cls.target_id.label('payment_group_payment_id'), func.count(cls.abacus_event_id).label('approval_count'), ) .where( cls.event_name == 'payment_approval', cls.target_id == payment_group_payment_id, cls.target_type == 'payment_group_payment', cls.rolled_back_at.is_(None), ) .group_by(cls.target_id) ) return db.session.execute(stmt).first() @classmethod def update_rollback_time(cls, target_type, target_id, event_name): """Update rolled_back_at field for a specified events.""" filter_columns = [ cls.target_type == target_type, cls.target_id == target_id, cls.event_name == event_name, cls.rolled_back_at.is_(None), ] stmt = ( update(cls) .where(*filter_columns) .values(rolled_back_at=CRUDMixin.current_timestamp()) ) db.session.execute(stmt) db.session.flush() stmt = select(cls).where( *filter_columns[:-1] ) # Exclude rolled_back_at filter for return return db.session.execute(stmt).scalars().all() @classmethod def get_events_by_target_type(cls, target_type, target_id, event_name=None): """Fetch all active events for a single target, optionally filtered by event_name.""" # Delegates to the batch query so the active_events() filter and WHERE live # in one place; the single- and many-id paths cannot drift. return cls.get_events_by_target_ids(target_type, [target_id], event_name) @classmethod def get_events_by_target_ids(cls, target_type, target_ids, event_name=None): """Get active events for a target type across one or many target ids in one query. The single source of the active_events() filter (rolled-back events excluded) for both the per-item and batch read paths. """ stmt = cls.active_events().where( cls.target_type == target_type, cls.target_id.in_(target_ids), ) if event_name is not None: stmt = stmt.where(cls.event_name == event_name) return db.session.execute(stmt).scalars().all() @classmethod def get_by_id(cls, obj_id): """Get object from DB by ID property, excluding rolled back events. :return: object """ stmt = cls.active_events().where(cls.abacus_event_id == obj_id) return db.session.execute(stmt).scalars().first() @classmethod def get_filtered_query(cls, event_ids=None): """Get events by field values.""" stmt = select(cls) # AbacusEvent IDs Filter if event_ids is not None: stmt = stmt.where(cls.abacus_event_id.in_(event_ids)) return stmt