from __future__ import annotations from collections.abc import Callable from datetime import UTC, datetime from decimal import Decimal from typing import TYPE_CHECKING from uuid import uuid4 from generalresearch.managers.thl.user_compensate import user_compensate from generalresearch.models.thl.definitions import ( Status, ) from generalresearch.models.thl.ledger import ( TransactionType, UserLedgerTransactionTypesSummary, UserLedgerTransactionTypeSummary, ) from generalresearch.models.thl.wallet.definitions import PayoutType if TYPE_CHECKING: from generalresearch.config import GRLBaseSettings from generalresearch.managers.thl.ledger_manager.ledger import LedgerManager from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager from generalresearch.managers.thl.payout import UserPayoutEventManager from generalresearch.models.thl.product import Product from generalresearch.models.thl.session import Session from generalresearch.models.thl.user import User def test_user_txs( user_factory: Callable[..., User], product_amt_true: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, delete_ledger_db: Callable[..., None], session_with_tx_factory: Callable[..., Session], adj_to_fail_with_tx_factory: Callable[..., None], adj_to_complete_with_tx_factory: Callable[..., None], session_factory: Callable[..., Session], user_payout_event_manager: UserPayoutEventManager, utc_now: datetime, settings: GRLBaseSettings, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_amt_true) account = thl_ledger_manager.get_account_or_create_user_wallet(user) print(f"{account.uuid=}") s: Session = session_with_tx_factory(user=user, wall_req_cpi=Decimal("1.00")) user_compensate( ledger_manager=thl_ledger_manager, user=user, amount_int=100, ) pe = user_payout_event_manager.create( uuid=uuid4().hex, debit_account_uuid=account.uuid, cashout_method_uuid=settings.amt_assignment_cashout_method_id, amount=5, created=utc_now, payout_type=PayoutType.AMT_HIT, request_data={}, ) thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, ) pe = user_payout_event_manager.create( uuid=uuid4().hex, debit_account_uuid=account.uuid, cashout_method_uuid=settings.amt_bonus_cashout_method_id, amount=127, created=utc_now, payout_type=PayoutType.AMT_BONUS, request_data={}, ) thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, ) wall = s.wall_events[-1] adj_to_fail_with_tx_factory(session=s, created=wall.finished) # And a fail -> complete adjustment s_fail: Session = session_factory( user=user, wall_count=1, final_status=Status.FAIL, wall_req_cpi=Decimal("2.00"), ) adj_to_complete_with_tx_factory(session=s_fail, created=utc_now) # txs = thl_ledger_manager.get_tx_filtered_by_account(account.uuid) # print(len(txs), txs) txs = thl_ledger_manager.get_user_txs(user) assert len(txs.transactions) == 6 assert txs.total == 6 assert txs.page == 1 assert txs.size == 50 # print(len(txs.transactions), txs) # d = txs.model_dump_json() # print(d) descriptions = {x.description for x in txs.transactions} assert descriptions == { "Compensation Bonus", "HIT Bonus", "HIT Reward", "Task Adjustment", "Task Complete", } amounts = {x.amount for x in txs.transactions} assert amounts == {-127, 100, 38, -38, -5, 76} assert txs.summary == UserLedgerTransactionTypesSummary( bp_adjustment=UserLedgerTransactionTypeSummary( entry_count=2, min_amount=-38, max_amount=76, total_amount=76 - 38 ), bp_payment=UserLedgerTransactionTypeSummary( entry_count=1, min_amount=38, max_amount=38, total_amount=38 ), user_bonus=UserLedgerTransactionTypeSummary( entry_count=1, min_amount=100, max_amount=100, total_amount=100 ), user_payout_request=UserLedgerTransactionTypeSummary( entry_count=2, min_amount=-127, max_amount=-5, total_amount=-132 ), ) tx_adj_c = [ tx for tx in txs.transactions if tx.tx_type == TransactionType.BP_ADJUSTMENT ] assert sorted([tx.amount for tx in tx_adj_c]) == [-38, 76] def test_user_payout_cancel_to_user_tx( user_factory: Callable[..., User], product_amt_true: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, delete_ledger_db: Callable[..., None], user_payout_event_manager: UserPayoutEventManager, settings: GRLBaseSettings, ): delete_ledger_db() create_main_accounts() user = user_factory(product=product_amt_true) account = thl_ledger_manager.get_account_or_create_user_wallet(user) pe = user_payout_event_manager.create( uuid=uuid4().hex, debit_account_uuid=account.uuid, cashout_method_uuid=settings.amt_bonus_cashout_method_id, amount=100, payout_type=PayoutType.AMT_BONUS, request_data={}, ) thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_wallet_balance_check=True, ) thl_ledger_manager.create_tx_user_payout_cancelled(user=user, payout_event=pe) txs = thl_ledger_manager.get_user_txs(user) cancel = next( tx for tx in txs.transactions if tx.tx_type == TransactionType.USER_PAYOUT_CANCEL ) assert cancel.amount == 100 assert cancel.description == "Payout Cancelled" assert cancel.payout_id == pe.uuid assert cancel.url == f"https://fsb.generalresearch.com/{user.product_id}/cashout/{pe.uuid}/" def test_user_txs_pagination( user_factory: Callable[..., User], product_amt_true: Product, create_main_accounts: Callable[..., None], ledger_manager: LedgerManager, thl_ledger_manager: ThlLedgerManager, delete_ledger_db: Callable[..., None], ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_amt_true) account = thl_ledger_manager.get_account_or_create_user_wallet(user) print(f"{account.uuid=}") for _ in range(12): user_compensate( ledger_manager=thl_ledger_manager, user=user, amount_int=100, skip_flag_check=True, ) txs = thl_ledger_manager.get_user_txs(user, page=1, size=5) assert len(txs.transactions) == 5 assert txs.total == 12 assert txs.page == 1 assert txs.size == 5 assert txs.summary.user_bonus.total_amount == 1200 assert txs.summary.user_bonus.entry_count == 12 # Skip to the 3rd page. We made 12, so there are 2 left txs = thl_ledger_manager.get_user_txs(user, page=3, size=5) assert len(txs.transactions) == 2 assert txs.total == 12 assert txs.page == 3 assert txs.summary.user_bonus.total_amount == 1200 assert txs.summary.user_bonus.entry_count == 12 # Should be empty, not fail txs = thl_ledger_manager.get_user_txs(user, page=4, size=5) assert len(txs.transactions) == 0 assert txs.total == 12 assert txs.page == 4 assert txs.summary.user_bonus.total_amount == 1200 assert txs.summary.user_bonus.entry_count == 12 # Test filtering. We should pull back only this one now = datetime.now(tz=UTC) user_compensate( ledger_manager=thl_ledger_manager, user=user, amount_int=100, skip_flag_check=True, ) txs = thl_ledger_manager.get_user_txs(user, page=1, size=5, time_start=now) assert len(txs.transactions) == 1 assert txs.total == 1 assert txs.page == 1 # And the summary is restricted to this time range also! assert txs.summary.user_bonus.total_amount == 100 assert txs.summary.user_bonus.entry_count == 1 # And filtering with 0 results now = datetime.now(tz=UTC) txs = thl_ledger_manager.get_user_txs(user, page=1, size=5, time_start=now) assert len(txs.transactions) == 0 assert txs.total == 0 assert txs.page == 1 assert txs.pages == 0 # And the summary is restricted to this time range also! assert txs.summary.user_bonus.total_amount == None assert txs.summary.user_bonus.entry_count == 0 def test_user_txs_rolling_balance( user_factory: Callable[..., User], product_amt_true: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, delete_ledger_db: Callable[..., None], user_payout_event_manager: UserPayoutEventManager, settings: GRLBaseSettings, ): """ Creates 3 $1.00 bonuses (postive), then 1 cashout (negative), $1.50 then 3 more $1.00 bonuses. Note: pagination + rolling balance will BREAK if txs have identical timestamps. In practice, they do not. """ delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_amt_true) account = thl_ledger_manager.get_account_or_create_user_wallet(user) for _ in range(3): user_compensate( ledger_manager=thl_ledger_manager, user=user, amount_int=100, skip_flag_check=True, ) pe = user_payout_event_manager.create( uuid=uuid4().hex, debit_account_uuid=account.uuid, cashout_method_uuid=settings.amt_bonus_cashout_method_id, amount=150, payout_type=PayoutType.AMT_BONUS, request_data={}, ) thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, ) for _ in range(3): user_compensate( ledger_manager=thl_ledger_manager, user=user, amount_int=100, skip_flag_check=True, ) txs = thl_ledger_manager.get_user_txs(user, page=1, size=10) assert txs.transactions[0].balance_after == 100 assert txs.transactions[1].balance_after == 200 assert txs.transactions[2].balance_after == 300 assert txs.transactions[3].balance_after == 150 assert txs.transactions[4].balance_after == 250 assert txs.transactions[5].balance_after == 350 assert txs.transactions[6].balance_after == 450 # Ascending order, get 2nd page, make sure the balances include # the previous txs. (will return last 3 txs) txs = thl_ledger_manager.get_user_txs(user, page=2, size=4) assert len(txs.transactions) == 3 assert txs.transactions[0].balance_after == 250 assert txs.transactions[1].balance_after == 350 assert txs.transactions[2].balance_after == 450 # Descending order, get 1st page. Will # return most recent 3 txs in desc order txs = thl_ledger_manager.get_user_txs(user, page=1, size=3, order_by="-created") assert len(txs.transactions) == 3 assert txs.transactions[0].balance_after == 450 assert txs.transactions[1].balance_after == 350 assert txs.transactions[2].balance_after == 250