from __future__ import annotations import logging from collections.abc import Callable from datetime import UTC, datetime, timedelta from decimal import Decimal from random import randint from uuid import uuid4 import pytest from generalresearch.currency import LedgerCurrency, USDCent from generalresearch.managers.thl.ledger_manager.ledger import ( LedgerManager, LedgerTransaction, ) from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager from generalresearch.managers.thl.product import ProductManager from generalresearch.managers.thl.session import SessionManager from generalresearch.managers.thl.wall import WallManager from generalresearch.models import Source from generalresearch.models.thl.definitions import ( WALL_ALLOWED_STATUS_STATUS_CODE, ) from generalresearch.models.thl.ledger import ( AccountType, Direction, LedgerAccount, TransactionType, ) from generalresearch.models.thl.payout import UserPayoutEvent from generalresearch.models.thl.product import ( PayoutConfig, PayoutTransformation, Product, UserWalletConfig, ) from generalresearch.models.thl.session import ( Session, Status, StatusCode1, Wall, WallAdjustedStatus, ) from generalresearch.models.thl.user import User from generalresearch.models.thl.wallet import PayoutType logger = logging.getLogger("LedgerManager") class TestThlLedgerTxManager: def test_create_tx_task_complete( self, wall: Wall, user: User, account_revenue_task_complete: LedgerAccount, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): create_main_accounts() tx = thl_ledger_manager.create_tx_task_complete(wall=wall, user=user) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.created == tx.created def test_create_tx_task_complete_( self, wall: Wall, user: User, account_revenue_task_complete: LedgerAccount, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): tx = thl_ledger_manager.create_tx_task_complete_(wall=wall, user=user) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.created == tx.created def test_create_tx_bp_payment( self, session_factory: Callable[..., Session], user: User, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, session_manager: SessionManager, ): delete_ledger_db() create_main_accounts() s1 = session_factory(user=user) _, status_code_1 = s1.determine_session_status() _, _, bp_pay, user_pay = s1.determine_payments() session_manager.finish_with_status( session=s1, status=Status.COMPLETE, status_code_1=status_code_1, finished=datetime.now(tz=UTC) + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) tx = thl_ledger_manager.create_tx_bp_payment(session=s1) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.created == tx.created def test_create_tx_bp_payment_amt( self, session_factory: Callable[..., Session], user_factory: Callable[..., User], product_manager: ProductManager, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, session_manager: SessionManager, ): delete_ledger_db() create_main_accounts() product = product_manager.create_dummy( payout_config=PayoutConfig( payout_transformation=PayoutTransformation( f="payout_transformation_amt" ) ), user_wallet_config=UserWalletConfig(amt=True, enabled=True), ) user = user_factory(product=product) s1 = session_factory(user=user, wall_req_cpi=Decimal(1)) status, status_code_1 = s1.determine_session_status() assert status == Status.COMPLETE thl_net, commission_amount, bp_pay, user_pay = s1.determine_payments( thl_ledger_manager=thl_ledger_manager ) print(thl_net, commission_amount, bp_pay, user_pay) session_manager.finish_with_status( session=s1, status=Status.COMPLETE, status_code_1=status_code_1, finished=datetime.now(tz=UTC) + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) tx = thl_ledger_manager.create_tx_bp_payment(session=s1) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.created == tx.created def test_create_tx_bp_payment_( self, session_factory: Callable[..., Session], user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, session_manager: SessionManager, utc_hour_ago: datetime, ): s1 = session_factory(user=user) status, status_code_1 = s1.determine_session_status() _, _, bp_pay, user_pay = s1.determine_payments() session_manager.finish_with_status( session=s1, status=status, status_code_1=status_code_1, finished=utc_hour_ago + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) s1.determine_payments() tx = thl_ledger_manager.create_tx_bp_payment_(session=s1) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.created == tx.created def test_create_tx_task_adjustment( self, wall_factory: Callable[..., Wall], session: Session, user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): """Create Wall event Complete, and Create a Tx Task Adjustment - I don't know what this does exactly... but we can confirm the transaction comes back with balanced amounts, and that the name of the Source is in the Tx description """ wall_status = Status.COMPLETE wall: Wall = wall_factory(session=session, wall_status=wall_status) tx = thl_ledger_manager.create_tx_task_adjustment(wall=wall, user=user) assert isinstance(tx, LedgerTransaction) res = ledger_manager.get_tx_by_id(transaction_id=tx.id) assert res.entries[0].amount == int(wall.cpi * 100) assert res.entries[1].amount == int(wall.cpi * 100) assert wall.source.name in res.ext_description assert res.created == tx.created def test_create_tx_bp_adjustment( self, session: Session, user: User, caplog, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): status, status_code_1 = session.determine_session_status() thl_net, commission_amount, bp_pay, user_pay = session.determine_payments() # The default session fixture is just an unfinished wall event assert len(session.wall_events) == 1 assert session.finished is None assert status == Status.TIMEOUT assert status_code_1 in list( WALL_ALLOWED_STATUS_STATUS_CODE.get(Status.TIMEOUT, {}) ) assert thl_net == Decimal(0) assert commission_amount == Decimal(0) assert bp_pay == Decimal(0) assert user_pay is None # Update the finished timestamp, but nothing else. This means that # there is no financial changes needed session.update(finished=datetime.now(tz=UTC) + timedelta(minutes=10)) assert session.finished with caplog.at_level(logging.INFO): tx = thl_ledger_manager.create_tx_bp_adjustment(session=session) assert tx is None assert "No transactions needed." in caplog.text def test_create_tx_bp_payout( self, product: Product, caplog, thl_ledger_manager: ThlLedgerManager, currency: LedgerCurrency, ): rand_amount: USDCent = USDCent(randint(100, 1_000)) payoutevent_uuid = uuid4().hex # Create a BP Payout for a Product without any activity. By issuing, # the skip_* checks, we should be able to force it to work, and will # then ultimately result in a negative balance tx = thl_ledger_manager.create_tx_bp_payout( product=product, amount=rand_amount, payoutevent_uuid=payoutevent_uuid, created=datetime.now(tz=UTC), skip_wallet_balance_check=True, skip_one_per_day_check=True, skip_flag_check=True, ) # Check the basic attributes assert isinstance(tx, LedgerTransaction) assert tx.ext_description == "BP Payout" assert ( tx.tag == f"{thl_ledger_manager.currency.value}:{TransactionType.BP_PAYOUT.value}:{payoutevent_uuid}" ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount # Check the Product's balance, it should be negative the amount that was # paid out. That's because the Product earned nothing.. and then was # sent something. balance = thl_ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_bp_wallet(product=product) ) assert balance == int(rand_amount) * -1 # Test some basic assertions with caplog.at_level(logging.INFO): with pytest.raises(expected_exception=Exception): thl_ledger_manager.create_tx_bp_payout( product=product, amount=rand_amount, payoutevent_uuid=uuid4().hex, created=datetime.now(tz=UTC), skip_wallet_balance_check=False, skip_one_per_day_check=False, skip_flag_check=False, ) assert "failed condition check >1 tx per day" in caplog.text def test_create_tx_bp_payout_( self, product: Product, thl_lm: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): rand_amount: USDCent = USDCent(randint(100, 1_000)) payoutevent_uuid = uuid4().hex # Create a BP Payout for a Product without any activity. tx = thl_lm.create_tx_bp_payout_( product=product, amount=rand_amount, payoutevent_uuid=payoutevent_uuid, created=datetime.now(tz=UTC), ) # Check the basic attributes assert isinstance(tx, LedgerTransaction) assert tx.ext_description == "BP Payout" assert ( tx.tag == f"{currency.value}:{TransactionType.BP_PAYOUT.value}:{payoutevent_uuid}" ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount def test_create_tx_plug_bp_wallet( self, product: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): """A BP Wallet "plug" is a way to makeup discrepancies and simply add or remove money """ rand_amount: USDCent = USDCent(randint(100, 1_000)) tx = thl_ledger_manager.create_tx_plug_bp_wallet( product=product, amount=rand_amount, created=datetime.now(tz=UTC), direction=Direction.DEBIT, skip_flag_check=False, ) assert isinstance(tx, LedgerTransaction) # We issued the BP money they didn't earn, so now they have a # negative balance balance = thl_ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_bp_wallet(product=product) ) assert balance == int(rand_amount) * -1 def test_create_tx_plug_bp_wallet_( self, product: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): """A BP Wallet "plug" is a way to fix discrepancies and simply add or remove money. Similar to above, but because it's unprotected, we can immediately issue another to see if the balance changes """ rand_amount: USDCent = USDCent(randint(100, 1_000)) tx = thl_ledger_manager.create_tx_plug_bp_wallet_( product=product, amount=rand_amount, created=datetime.now(tz=UTC), direction=Direction.DEBIT, ) assert isinstance(tx, LedgerTransaction) # We issued the BP money they didn't earn, so now they have a # negative balance balance = thl_ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_bp_wallet(product=product) ) assert balance == int(rand_amount) * -1 # Issue a positive one now, and confirm the balance goes positive thl_ledger_manager.create_tx_plug_bp_wallet_( product=product, amount=rand_amount + rand_amount, created=datetime.now(tz=UTC), direction=Direction.CREDIT, ) balance = thl_ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_bp_wallet(product=product) ) assert balance == int(rand_amount) def test_create_tx_user_payout_request( self, user: User, product_user_wallet_yes: Product, user_factory: Callable[..., User], delete_df_collection: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=500, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) # The default user fixture uses a product that doesn't have wallet # mode enabled with pytest.raises(expected_exception=AssertionError): thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) # Now try it for a user on a product with wallet mode u2 = user_factory(product=product_user_wallet_yes) # User's pre-balance is 0 because no activity has occurred yet pre_balance = ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_user_wallet(user=u2) ) assert pre_balance == 0 tx = thl_ledger_manager.create_tx_user_payout_request( user=u2, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) assert isinstance(tx, LedgerTransaction) assert tx.entries[0].amount == pe.amount assert tx.entries[1].amount == pe.amount assert tx.ext_description == "User Payout Paypal Request $5.00" # # (TODO): This key ":user_payout:" is NOT part of the TransactionType # enum and was manually set. It should be based off the # TransactionType names. # assert tx.tag == f"{currency.value}:user_payout:{pe.uuid}:request" # Post balance is -$5.00 because it comes out of the wallet before # it's Approved or Completed post_balance = ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_user_wallet(user=u2) ) assert post_balance == -500 def test_create_tx_user_payout_request_( self, user: User, product_user_wallet_yes: Product, user_factory: Callable[..., User], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): delete_ledger_db() pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=500, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) rand_description = uuid4().hex tx = thl_ledger_manager.create_tx_user_payout_request_( user=user, payout_event=pe, description=rand_description ) assert tx.ext_description == rand_description post_balance = ledger_manager.get_account_balance( account=thl_ledger_manager.get_account_or_create_user_wallet(user=user) ) assert post_balance == -500 def test_create_tx_user_payout_complete( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) # Ensure the user starts out with nothing... assert ledger_manager.get_account_balance(account=user_account) == 0 pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=rand_amount, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) # Confirm it's not possible unless a request occurred happen with pytest.raises(expected_exception=ValueError): thl_ledger_manager.create_tx_user_payout_complete( user=user, payout_event=pe, fee_amount=None, skip_flag_check=False, ) # (1) Make a request first thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) # Assert the balance came out of their user wallet assert ( ledger_manager.get_account_balance(account=user_account) == rand_amount * -1 ) # (2) Complete the request tx = thl_ledger_manager.create_tx_user_payout_complete( user=user, payout_event=pe, fee_amount=Decimal(0), skip_flag_check=False, ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount assert tx.tag == f"{currency.value}:user_payout:{pe.uuid}:complete" assert isinstance(tx, LedgerTransaction) # The amount that comes out of the user wallet doesn't change after # it's approved becuase it's already been withdrawn assert ( ledger_manager.get_account_balance(account=user_account) == rand_amount * -1 ) def test_create_tx_user_payout_complete_( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, ): user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=rand_amount, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) # (1) Make a request first thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) # (2) Complete the request rand_desc = uuid4().hex bp_expense_account = thl_ledger_manager.get_account_or_create_bp_expense( product=user.product, expense_name="paypal" ) bp_wallet_account = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) tx = thl_ledger_manager.create_tx_user_payout_complete_( user=user, payout_event=pe, fee_amount=Decimal("0.00"), fee_expense_account=bp_expense_account, fee_payer_account=bp_wallet_account, description=rand_desc, ) assert tx.ext_description == rand_desc assert ( ledger_manager.get_account_balance(account=user_account) == rand_amount * -1 ) def test_create_tx_user_payout_cancelled( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=rand_amount, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) # (1) Make a request first thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) # Assert the balance came out of their user wallet assert ( ledger_manager.get_account_balance(account=user_account) == rand_amount * -1 ) # (2) Cancel the request tx = thl_ledger_manager.create_tx_user_payout_cancelled( user=user, payout_event=pe, skip_flag_check=False, ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount assert tx.tag == f"{currency.value}:user_payout:{pe.uuid}:cancel" assert isinstance(tx, LedgerTransaction) # Assert the balance comes back to 0 after it was cancelled assert ledger_manager.get_account_balance(account=user_account) == 0 def test_create_tx_user_payout_cancelled_( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) pe = UserPayoutEvent( uuid=uuid4().hex, payout_type=PayoutType.PAYPAL, amount=rand_amount, cashout_method_uuid=uuid4().hex, debit_account_uuid=uuid4().hex, ) # (1) Make a request first thl_ledger_manager.create_tx_user_payout_request( user=user, payout_event=pe, skip_flag_check=True, skip_wallet_balance_check=True, ) # Assert the balance came out of their user wallet assert ( ledger_manager.get_account_balance(account=user_account) == rand_amount * -1 ) # (2) Cancel the request rand_desc = uuid4().hex tx = thl_ledger_manager.create_tx_user_payout_cancelled_( user=user, payout_event=pe, description=rand_desc ) assert isinstance(tx, LedgerTransaction) assert tx.ext_description == rand_desc assert ledger_manager.get_account_balance(account=user_account) == 0 def test_create_tx_user_bonus( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) rand_ref_uuid = uuid4().hex rand_desc = uuid4().hex # Assert the balance came out of their user wallet assert ledger_manager.get_account_balance(account=user_account) == 0 tx = thl_ledger_manager.create_tx_user_bonus( user=user, amount=Decimal(rand_amount / 100), ref_uuid=rand_ref_uuid, description=rand_desc, skip_flag_check=True, ) assert tx.ext_description == rand_desc assert ( tx.tag == f"{thl_ledger_manager.currency.value}:user_bonus:{rand_ref_uuid}" ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount # Assert the balance came out of their user wallet assert ledger_manager.get_account_balance(account=user_account) == rand_amount def test_create_tx_user_bonus_( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): user: User = user_factory(product=product_user_wallet_yes) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) rand_amount = randint(100, 1_000) rand_ref_uuid = uuid4().hex rand_desc = uuid4().hex # Assert the balance came out of their user wallet assert ledger_manager.get_account_balance(account=user_account) == 0 tx = thl_ledger_manager.create_tx_user_bonus_( user=user, amount=Decimal(rand_amount / 100), ref_uuid=rand_ref_uuid, description=rand_desc, ) assert tx.ext_description == rand_desc assert ( tx.tag == f"{thl_ledger_manager.currency.value}:user_bonus:{rand_ref_uuid}" ) assert tx.entries[0].amount == rand_amount assert tx.entries[1].amount == rand_amount # Assert the balance came out of their user wallet assert ledger_manager.get_account_balance(account=user_account) == rand_amount class TestThlLedgerTxManagerFlows: """Combine the various THL_LM methods to create actual "real world" examples """ def test_create_tx_task_complete( self, user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, delete_ledger_db: Callable[..., None], ): delete_ledger_db() create_main_accounts() wall1 = Wall( user_id=1, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("1.23"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) wall2 = Wall( user_id=1, source=Source.FULL_CIRCLE, req_survey_id="yyy", req_cpi=Decimal("3.21"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete( wall=wall2, user=user, created=wall2.started ) cash = thl_ledger_manager.get_account_cash() revenue = thl_ledger_manager.get_account_task_complete_revenue() assert ledger_manager.get_account_balance(cash) == 123 + 321 assert ledger_manager.get_account_balance(revenue) == 123 + 321 assert ledger_manager.check_ledger_balanced() assert ( ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="d" ) == 123 ) assert ( ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="f" ) == 321 ) assert ( ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="x" ) == 0 ) assert ( thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="thl_wall", metadata_value=wall1.uuid, ) == 123 ) def test_create_transaction_task_complete_1_cent( self, user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): wall1 = Wall( user_id=1, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("0.007"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) assert isinstance(tx, LedgerTransaction) def test_create_transaction_bp_payment( self, user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, delete_ledger_db: Callable[..., None], session_factory: Callable[..., Session], utc_hour_ago: datetime, ): delete_ledger_db() create_main_accounts() s1: Session = session_factory( user=user, wall_count=1, started=utc_hour_ago, wall_source=Source.TESTING, ) w1: Wall = s1.wall_events[0] tx = thl_ledger_manager.create_tx_task_complete( wall=w1, user=user, created=w1.started ) assert isinstance(tx, LedgerTransaction) status, status_code_1 = s1.determine_session_status() thl_net, commission_amount, bp_pay, user_pay = s1.determine_payments() s1.update( status=status, status_code_1=status_code_1, finished=s1.started + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) print(thl_net, commission_amount, bp_pay, user_pay) thl_ledger_manager.create_tx_bp_payment(session=s1, created=w1.started) revenue = thl_ledger_manager.get_account_task_complete_revenue() bp_wallet = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) bp_commission = thl_ledger_manager.get_account_or_create_bp_commission( product=user.product ) assert 0 == ledger_manager.get_account_balance(account=revenue) assert 50 == ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value=Source.TESTING, ) assert 48 == ledger_manager.get_account_balance(account=bp_wallet) assert 48 == ledger_manager.get_account_filtered_balance( account=bp_wallet, metadata_key="thl_session", metadata_value=s1.uuid, ) assert 2 == thl_ledger_manager.get_account_balance(account=bp_commission) assert thl_ledger_manager.check_ledger_balanced() def test_create_transaction_bp_payment_round( self, user_factory: Callable[..., User], product_user_wallet_no: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): product_user_wallet_no.commission_pct = Decimal("0.085") user: User = user_factory(product=product_user_wallet_no) wall1 = Wall( user_id=user.user_id, source=Source.SAGO, req_survey_id="xxx", req_cpi=Decimal("0.287"), session_id=3, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) assert isinstance(tx, LedgerTransaction) session = Session(started=wall1.started, user=user, wall_events=[wall1]) status, status_code_1 = session.determine_session_status() thl_net, commission_amount, bp_pay, user_pay = session.determine_payments() session.update( status=status, status_code_1=status_code_1, finished=session.started + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) print(thl_net, commission_amount, bp_pay, user_pay) tx = thl_ledger_manager.create_tx_bp_payment( session=session, created=wall1.started ) assert isinstance(tx, LedgerTransaction) def test_create_transaction_bp_payment_round2( self, delete_ledger_db: Callable[..., None], user: User, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() # user must be no user wallet # e.g. session 869b5bfa47f44b4f81cd095ed01df2ff this fails if you dont round properly wall1 = Wall( user_id=user.user_id, source=Source.SAGO, req_survey_id="xxx", req_cpi=Decimal("1.64500"), session_id=3, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) session = Session(started=wall1.started, user=user, wall_events=[wall1]) status, status_code_1 = session.determine_session_status() # thl_net, commission_amount, bp_pay, user_pay = session.determine_payments() session.update( status=status, status_code_1=status_code_1, finished=session.started + timedelta(minutes=10), payout=Decimal("1.53"), user_payout=Decimal("1.53"), ) thl_ledger_manager.create_tx_bp_payment(session=session, created=wall1.started) def test_create_transaction_bp_payment_round3( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): # e.g. session ___ fails b/c we rounded incorrectly # before, and now we are off by a penny... user: User = user_factory(product=product_user_wallet_yes) wall1 = Wall( user_id=user.user_id, source=Source.SAGO, req_survey_id="xxx", req_cpi=Decimal("0.385"), session_id=3, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=datetime.now(UTC), finished=datetime.now(UTC) + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) session = Session(started=wall1.started, user=user, wall_events=[wall1]) status, status_code_1 = session.determine_session_status() # thl_net, commission_amount, bp_pay, user_pay = session.determine_payments() session.update( status=status, status_code_1=status_code_1, finished=session.started + timedelta(minutes=10), payout=Decimal("0.39"), user_payout=Decimal("0.26"), ) # with pytest.logs(logger, level=logging.WARNING) as cm: # tx = thl_lm.create_transaction_bp_payment(session, created=wall1.started) # assert "Capping bp_pay to thl_net" in cm.output[0] def test_create_transaction_bp_payment_user_wallet( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, session_manager: SessionManager, wall_manager: WallManager, ledger_manager: LedgerManager, session_factory: Callable[..., Session], currency: LedgerCurrency, utc_hour_ago: datetime, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_user_wallet_yes) assert isinstance(user.product, Product) assert user.product.user_wallet_enabled s1: Session = session_factory( user=user, wall_count=1, started=utc_hour_ago, wall_req_cpi=Decimal(".50"), wall_source=Source.TESTING, ) w1: Wall = s1.wall_events[0] thl_ledger_manager.create_tx_task_complete( wall=w1, user=user, created=w1.started ) status, status_code_1 = s1.determine_session_status() _, _, bp_pay, user_pay = s1.determine_payments() session_manager.finish_with_status( session=s1, status=status, status_code_1=status_code_1, finished=s1.started + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) thl_ledger_manager.create_tx_bp_payment(session=s1, created=w1.started) revenue = thl_ledger_manager.get_account_task_complete_revenue() bp_wallet = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) bp_commission = thl_ledger_manager.get_account_or_create_bp_commission( product=user.product ) user_wallet = thl_ledger_manager.get_account_or_create_user_wallet(user=user) assert 0 == thl_ledger_manager.get_account_balance(account=revenue) assert 50 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value=Source.TESTING, ) assert 48 - 19 == thl_ledger_manager.get_account_balance(account=bp_wallet) assert 48 - 19 == thl_ledger_manager.get_account_filtered_balance( account=bp_wallet, metadata_key="thl_session", metadata_value=s1.uuid, ) assert 2 == thl_ledger_manager.get_account_balance(bp_commission) assert 19 == thl_ledger_manager.get_account_balance(user_wallet) assert 19 == thl_ledger_manager.get_account_filtered_balance( account=user_wallet, metadata_key="thl_session", metadata_value=s1.uuid, ) assert 0 == thl_ledger_manager.get_account_filtered_balance( account=user_wallet, metadata_key="thl_session", metadata_value="x" ) assert thl_ledger_manager.check_ledger_balanced() class TestThlLedgerManagerAdj: def test_create_tx_task_adjustment( self, user_factory: Callable[..., User], product_user_wallet_no: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, utc_hour_ago: datetime, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_user_wallet_no) wall1 = Wall( user_id=user.user_id, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("1.23"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=utc_hour_ago, finished=utc_hour_ago + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete(wall1, user, created=wall1.started) wall2 = Wall( user_id=1, source=Source.FULL_CIRCLE, req_survey_id="yyy", req_cpi=Decimal("3.21"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=utc_hour_ago, finished=utc_hour_ago + timedelta(seconds=1), ) thl_ledger_manager.create_tx_task_complete(wall2, user, created=wall2.started) wall1.update( adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, adjusted_cpi=0, adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) print(wall1.get_cpi_after_adjustment()) thl_ledger_manager.create_tx_task_adjustment(wall1, user) cash = thl_ledger_manager.get_account_cash() revenue = thl_ledger_manager.get_account_task_complete_revenue() assert 123 + 321 - 123 == thl_ledger_manager.get_account_balance(account=cash) assert 123 + 321 - 123 == thl_ledger_manager.get_account_balance( account=revenue ) assert thl_ledger_manager.check_ledger_balanced() assert 0 == thl_ledger_manager.get_account_filtered_balance( revenue, metadata_key="source", metadata_value="d" ) assert 321 == thl_ledger_manager.get_account_filtered_balance( revenue, metadata_key="source", metadata_value="f" ) assert 0 == thl_ledger_manager.get_account_filtered_balance( revenue, metadata_key="source", metadata_value="x" ) assert 123 - 123 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="thl_wall", metadata_value=wall1.uuid ) # un-reconcile it wall1.update( adjusted_status=None, adjusted_cpi=None, adjusted_timestamp=utc_hour_ago + timedelta(minutes=45), ) print(wall1.get_cpi_after_adjustment()) thl_ledger_manager.create_tx_task_adjustment(wall1, user) # and then run it again to make sure it does nothing thl_ledger_manager.create_tx_task_adjustment(wall1, user) cash = thl_ledger_manager.get_account_cash() revenue = thl_ledger_manager.get_account_task_complete_revenue() assert 123 + 321 - 123 + 123 == thl_ledger_manager.get_account_balance(cash) assert 123 + 321 - 123 + 123 == thl_ledger_manager.get_account_balance(revenue) assert thl_ledger_manager.check_ledger_balanced() assert 123 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="d" ) assert 321 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="f" ) assert 0 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="source", metadata_value="x" ) assert 123 - 123 + 123 == thl_ledger_manager.get_account_filtered_balance( account=revenue, metadata_key="thl_wall", metadata_value=wall1.uuid ) def test_create_tx_bp_adjustment( self, user: User, product_user_wallet_no: Product, create_main_accounts: Callable[..., None], caplog, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, session_manager: SessionManager, wall_manager: WallManager, session_factory: Callable[..., Session], utc_hour_ago: datetime, delete_ledger_db: Callable[..., None], ): delete_ledger_db() create_main_accounts() s1 = session_factory( user=user, wall_count=2, wall_req_cpis=[Decimal(1), Decimal(3)], wall_statuses=[Status.COMPLETE, Status.COMPLETE], started=utc_hour_ago, ) w1: Wall = s1.wall_events[0] w2: Wall = s1.wall_events[1] thl_ledger_manager.create_tx_task_complete( wall=w1, user=user, created=w1.started ) thl_ledger_manager.create_tx_task_complete( wall=w2, user=user, created=w2.started ) status, status_code_1 = s1.determine_session_status() _, _, bp_pay, user_pay = s1.determine_payments() session_manager.finish_with_status( session=s1, status=status, status_code_1=status_code_1, finished=utc_hour_ago + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) thl_ledger_manager.create_tx_bp_payment(session=s1, created=w1.started) revenue = thl_ledger_manager.get_account_task_complete_revenue() bp_wallet_account = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) bp_commission_account = thl_ledger_manager.get_account_or_create_bp_commission( product=user.product ) assert 380 == thl_ledger_manager.get_account_balance(account=bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(account=revenue) assert 20 == thl_ledger_manager.get_account_balance( account=bp_commission_account ) thl_ledger_manager.check_ledger_balanced() # This should do nothing (since we haven't adjusted any wall events) s1.adjust_status() with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session=s1) assert ( "create_transaction_bp_adjustment. No transactions needed." in caplog.text ) # self.assertEqual(380, ledger_manager.get_account_balance(bp_wallet_account)) # self.assertEqual(0, ledger_manager.get_account_balance(revenue)) # self.assertEqual(20, ledger_manager.get_account_balance(bp_commission_account)) # self.assertTrue(ledger_manager.check_ledger_balanced()) # recon $1 survey. wall_manager.adjust_status( wall=w1, adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, adjusted_cpi=Decimal(0), adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) thl_ledger_manager.create_tx_task_adjustment(wall=w1, user=user) # -$1.00 b/c the MP took the $1 back, but we haven't yet taken the BP payment back assert -100 == thl_ledger_manager.get_account_balance(revenue) s1.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=s1) with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session=s1) assert ( "create_transaction_bp_adjustment. No transactions needed." in caplog.text ) assert 380 - 95 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 - 5 == thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() # unrecon the $1 survey wall_manager.adjust_status( wall=w1, adjusted_status=None, adjusted_cpi=None, adjusted_timestamp=utc_hour_ago + timedelta(minutes=45), ) thl_ledger_manager.create_tx_task_adjustment( wall=w1, user=user, created=utc_hour_ago + timedelta(minutes=45), ) _, _, _ = s1.determine_new_status_and_payouts() s1.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=s1) assert 380 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20, thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() def test_create_tx_bp_adjustment_small( self, user_factory: Callable[..., User], product_user_wallet_no: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, utc_hour_ago: datetime, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() # This failed when I didn't check that `change_commission` > 0 in # create_transaction_bp_adjustment user: User = user_factory(product=product_user_wallet_no) wall1 = Wall( user_id=user.user_id, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("0.10"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=utc_hour_ago, finished=utc_hour_ago + timedelta(seconds=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) assert isinstance(tx, LedgerTransaction) session = Session(started=wall1.started, user=user, wall_events=[wall1]) status, status_code_1 = session.determine_session_status() _, _, bp_pay, user_pay = session.determine_payments() session.update( status=status, status_code_1=status_code_1, finished=utc_hour_ago + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) thl_ledger_manager.create_tx_bp_payment(session, created=wall1.started) wall1.update( adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, adjusted_cpi=0, adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) thl_ledger_manager.create_tx_task_adjustment(wall1, user) session.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session) def test_create_tx_bp_adjustment_abandon( self, user_factory: Callable[..., User], product_user_wallet_no: Product, delete_ledger_db: Callable[..., None], session_factory: Callable[..., Session], create_main_accounts: Callable[..., None], caplog, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, utc_hour_ago: datetime, session_manager: SessionManager, wall_manager: WallManager, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_user_wallet_no) s1: Session = session_factory( user=user, final_status=Status.ABANDON, wall_req_cpi=Decimal(1) ) w1 = s1.wall_events[-1] # Adjust to complete. wall_manager.adjust_status( wall=w1, adjusted_status=WallAdjustedStatus.ADJUSTED_TO_COMPLETE, adjusted_cpi=w1.cpi, adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) thl_ledger_manager.create_tx_task_adjustment(wall=w1, user=user) s1.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=s1) # And then adjust it back (it was abandon before, but now it should be # fail (?) or back to abandon?) wall_manager.adjust_status( wall=w1, adjusted_status=None, adjusted_cpi=None, adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) thl_ledger_manager.create_tx_task_adjustment(wall=w1, user=user) s1.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=s1) revenue = thl_ledger_manager.get_account_task_complete_revenue() bp_wallet_account = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) bp_commission_account = thl_ledger_manager.get_account_or_create_bp_commission( product=user.product ) assert 0 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 0 == thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() # This should do nothing s1.adjust_status() with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session=s1) assert "No transactions needed" in caplog.text # Now back to complete again wall_manager.adjust_status( wall=w1, adjusted_status=WallAdjustedStatus.ADJUSTED_TO_COMPLETE, adjusted_cpi=w1.cpi, adjusted_timestamp=utc_hour_ago + timedelta(hours=1), ) s1.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=s1) assert 95 == thl_ledger_manager.get_account_balance(bp_wallet_account) def test_create_tx_bp_adjustment_user_wallet( self, user_factory: Callable[..., User], product_user_wallet_yes: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], caplog, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() now = datetime.now(UTC) - timedelta(days=1) user: User = user_factory(product=product_user_wallet_yes) # Create 2 Wall completes and create the respective transaction for # them. We then create a 3rd wall event which is a failure but we # do NOT create a transaction for it wall3 = Wall( user_id=8, source=Source.CINT, req_survey_id="zzz", req_cpi=Decimal("2.00"), session_id=1, status=Status.FAIL, status_code_1=StatusCode1.BUYER_FAIL, started=now, finished=now + timedelta(minutes=1), ) now_w1 = now + timedelta(minutes=1, milliseconds=1) wall1 = Wall( user_id=user.user_id, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("1.00"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=now_w1, finished=now_w1 + timedelta(minutes=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) assert isinstance(tx, LedgerTransaction) now_w2 = now + timedelta(minutes=2, milliseconds=1) wall2 = Wall( user_id=user.user_id, source=Source.FULL_CIRCLE, req_survey_id="yyy", req_cpi=Decimal("3.00"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=now_w2, finished=now_w2 + timedelta(minutes=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall2, user=user, created=wall2.started ) assert isinstance(tx, LedgerTransaction) # It doesn't matter what order these wall events go in as because # we have a pydantic valiator that sorts them wall_events = [wall3, wall1, wall2] # shuffle(wall_events) session = Session(started=wall1.started, user=user, wall_events=wall_events) status, status_code_1 = session.determine_session_status() assert status == Status.COMPLETE assert status_code_1 == StatusCode1.COMPLETE thl_net, commission_amount, bp_pay, user_pay = session.determine_payments() assert thl_net == Decimal("4.00") assert commission_amount == Decimal("0.20") assert bp_pay == Decimal("3.80") assert user_pay == Decimal("1.52") session.update( status=status, status_code_1=status_code_1, finished=now + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) tx = thl_ledger_manager.create_tx_bp_adjustment( session=session, created=wall1.started ) assert isinstance(tx, LedgerTransaction) bp_wallet_account = thl_ledger_manager.get_account_or_create_bp_wallet( product=user.product ) assert 228 == thl_ledger_manager.get_account_balance(account=bp_wallet_account) user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user) assert 152 == thl_ledger_manager.get_account_balance(account=user_account) revenue = thl_ledger_manager.get_account_task_complete_revenue() assert 0 == thl_ledger_manager.get_account_balance(account=revenue) bp_commission_account = thl_ledger_manager.get_account_or_create_bp_commission( product=user.product ) assert 20 == thl_ledger_manager.get_account_balance( account=bp_commission_account ) # the total (4.00) = 2.28 + 1.52 + .20 assert thl_ledger_manager.check_ledger_balanced() # This should do nothing (since we haven't adjusted any wall events) session.adjust_status() print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session) assert ( "create_transaction_bp_adjustment. No transactions needed." in caplog.text ) # recon $1 survey. wall1.update( adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, adjusted_cpi=0, adjusted_timestamp=now + timedelta(hours=1), ) thl_ledger_manager.create_tx_task_adjustment(wall1, user) # -$1.00 b/c the MP took the $1 back, but we haven't yet taken the BP payment back assert -100 == thl_ledger_manager.get_account_balance(revenue) session.adjust_status() print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) thl_ledger_manager.create_tx_bp_adjustment(session) # running this twice b/c it should do nothing the 2nd time print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session) assert ( "create_transaction_bp_adjustment. No transactions needed." in caplog.text ) assert 228 - 57 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 152 - 38 == thl_ledger_manager.get_account_balance(user_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 - 5 == thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() # unrecon the $1 survey wall1.update( adjusted_status=None, adjusted_cpi=None, adjusted_timestamp=now + timedelta(hours=2), ) tx = thl_ledger_manager.create_tx_task_adjustment(wall=wall1, user=user) assert isinstance(tx, LedgerTransaction) new_status, new_payout, new_user_payout = ( session.determine_new_status_and_payouts() ) print(new_status, new_payout, new_user_payout, session.adjusted_payout) session.adjust_status() print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) thl_ledger_manager.create_tx_bp_adjustment(session) assert 228 - 57 + 57 == thl_ledger_manager.get_account_balance( bp_wallet_account ) assert 152 - 38 + 38 == thl_ledger_manager.get_account_balance(user_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 - 5 + 5 == thl_ledger_manager.get_account_balance( bp_commission_account ) assert thl_ledger_manager.check_ledger_balanced() # make the $2 failure into a complete also wall3.update( adjusted_status=WallAdjustedStatus.ADJUSTED_TO_COMPLETE, adjusted_cpi=wall3.cpi, adjusted_timestamp=now + timedelta(hours=2), ) thl_ledger_manager.create_tx_task_adjustment(wall3, user) new_status, new_payout, new_user_payout = ( session.determine_new_status_and_payouts() ) print(new_status, new_payout, new_user_payout, session.adjusted_payout) session.adjust_status() print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) thl_ledger_manager.create_tx_bp_adjustment(session) assert 228 - 57 + 57 + 114 == thl_ledger_manager.get_account_balance( bp_wallet_account ) assert 152 - 38 + 38 + 76 == thl_ledger_manager.get_account_balance( user_account ) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 - 5 + 5 + 10 == thl_ledger_manager.get_account_balance( bp_commission_account ) assert thl_ledger_manager.check_ledger_balanced() def test_create_transaction_bp_adjustment_cpi_adjustment( self, user_factory: Callable[..., User], product_user_wallet_no: Product, create_main_accounts: Callable[..., None], delete_ledger_db: Callable[..., None], caplog, thl_ledger_manager: ThlLedgerManager, ledger_manager: LedgerManager, utc_hour_ago: datetime, currency: LedgerCurrency, ): delete_ledger_db() create_main_accounts() user: User = user_factory(product=product_user_wallet_no) wall1 = Wall( user_id=user.user_id, source=Source.DYNATA, req_survey_id="xxx", req_cpi=Decimal("1.00"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=utc_hour_ago, finished=utc_hour_ago + timedelta(seconds=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall1, user=user, created=wall1.started ) assert isinstance(tx, LedgerTransaction) wall2 = Wall( user_id=user.user_id, source=Source.FULL_CIRCLE, req_survey_id="yyy", req_cpi=Decimal("3.00"), session_id=1, status=Status.COMPLETE, status_code_1=StatusCode1.COMPLETE, started=utc_hour_ago, finished=utc_hour_ago + timedelta(seconds=1), ) tx = thl_ledger_manager.create_tx_task_complete( wall=wall2, user=user, created=wall2.started ) assert isinstance(tx, LedgerTransaction) session = Session(started=wall1.started, user=user, wall_events=[wall1, wall2]) status, status_code_1 = session.determine_session_status() _, _, bp_pay, user_pay = session.determine_payments() session.update( status=status, status_code_1=status_code_1, finished=utc_hour_ago + timedelta(minutes=10), payout=bp_pay, user_payout=user_pay, ) thl_ledger_manager.create_tx_bp_payment(session, created=wall1.started) revenue = ththl_ledger_managerl_lm.get_account_task_complete_revenue() bp_wallet_account = thl_ledger_manager.get_account_or_create_bp_wallet( user.product ) bp_commission_account = thl_ledger_manager.get_account_or_create_bp_commission( user.product ) assert 380 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 == thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() # cpi adjustment $1 -> $.60. wall1.update( adjusted_status=WallAdjustedStatus.CPI_ADJUSTMENT, adjusted_cpi=Decimal("0.60"), adjusted_timestamp=utc_hour_ago + timedelta(minutes=30), ) thl_ledger_manager.create_tx_task_adjustment(wall1, user) # -$0.40 b/c the MP took $0.40 back, but we haven't yet taken the BP payment back assert -40 == thl_ledger_manager.get_account_balance(revenue) session.adjust_status() print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) thl_ledger_manager.create_tx_bp_adjustment(session) # running this twice b/c it should do nothing the 2nd time print( session.get_status_after_adjustment(), session.get_payout_after_adjustment(), session.get_user_payout_after_adjustment(), ) with caplog.at_level(logging.INFO): thl_ledger_manager.create_tx_bp_adjustment(session) assert "create_transaction_bp_adjustment." in caplog.text assert "No transactions needed." in caplog.text assert 380 - 38 == thl_ledger_manager.get_account_balance(bp_wallet_account) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 20 - 2 == thl_ledger_manager.get_account_balance(bp_commission_account) assert thl_ledger_manager.check_ledger_balanced() # adjust it to failure wall1.update( adjusted_status=WallAdjustedStatus.ADJUSTED_TO_FAIL, adjusted_cpi=0, adjusted_timestamp=utc_hour_ago + timedelta(minutes=45), ) thl_ledger_manager.create_tx_task_adjustment(wall1, user) session.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session) assert 300 - (300 * 0.05) == thl_ledger_manager.get_account_balance( bp_wallet_account ) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 300 * 0.05 == thl_ledger_manager.get_account_balance( bp_commission_account ) assert thl_ledger_manager.check_ledger_balanced() # and then back to cpi adj again, but this time for more than the orig amount wall1.update( adjusted_status=WallAdjustedStatus.CPI_ADJUSTMENT, adjusted_cpi=Decimal("2.00"), adjusted_timestamp=utc_hour_ago + timedelta(minutes=45), ) thl_ledger_manager.create_tx_task_adjustment(wall1, user) session.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session) assert 500 - (500 * 0.05) == thl_ledger_manager.get_account_balance( bp_wallet_account ) assert 0 == thl_ledger_manager.get_account_balance(revenue) assert 500 * 0.05 == thl_ledger_manager.get_account_balance( bp_commission_account ) assert thl_ledger_manager.check_ledger_balanced() # And adjust again wall1.update( adjusted_status=WallAdjustedStatus.CPI_ADJUSTMENT, adjusted_cpi=Decimal("3.00"), adjusted_timestamp=utc_hour_ago + timedelta(minutes=45), ) thl_ledger_manager.create_tx_task_adjustment(wall=wall1, user=user) session.adjust_status() thl_ledger_manager.create_tx_bp_adjustment(session=session) assert 600 - (600 * 0.05) == thl_ledger_manager.get_account_balance( account=bp_wallet_account ) assert 0 == thl_ledger_manager.get_account_balance(account=revenue) assert 600 * 0.05 == thl_ledger_manager.get_account_balance( account=bp_commission_account ) assert thl_ledger_manager.check_ledger_balanced()