diff options
| -rw-r--r-- | generalresearch/managers/thl/payout.py | 110 | ||||
| -rw-r--r-- | tests/managers/thl/test_payout.py | 168 |
2 files changed, 74 insertions, 204 deletions
diff --git a/generalresearch/managers/thl/payout.py b/generalresearch/managers/thl/payout.py index 4409a63..551758f 100644 --- a/generalresearch/managers/thl/payout.py +++ b/generalresearch/managers/thl/payout.py @@ -16,6 +16,10 @@ from generalresearch.currency import USDCent from generalresearch.decorators import LOG from generalresearch.managers.base import ( PostgresManagerWithRedis, + Permission, +) +from generalresearch.managers.thl.ledger_manager.exceptions import ( + LedgerTransactionConditionFailedError, ) from generalresearch.managers.thl.ledger_manager.thl_ledger import ( ThlLedgerManager, @@ -41,6 +45,8 @@ from generalresearch.models.thl.wallet.cashout_method import ( CashMailOrderData, CashoutRequestInfo, ) +from generalresearch.pg_helper import PostgresConfig +from generalresearch.redis_helper import RedisConfig class PayoutEventManager(PostgresManagerWithRedis): @@ -51,27 +57,6 @@ class PayoutEventManager(PostgresManagerWithRedis): """ - def set_account_lookup_table(self, thl_lm: ThlLedgerManager) -> None: - """This needs to run from grl-flow or from somewhere that has thl-redis - access - """ - - res = self.pg_config.execute_sql_query( - query=f""" - SELECT uuid, reference_uuid - FROM ledger_account - WHERE qualified_name LIKE '{thl_lm.currency.value}:bp_wallet:%' - """ - ) - account_to_product = {i["uuid"]: i["reference_uuid"] for i in res} - product_to_account = {i["reference_uuid"]: i["uuid"] for i in res} - - rc = self.redis_client - rc.hset(name="pem:account_to_product", mapping=account_to_product) - rc.hset(name="pem:product_to_account", mapping=product_to_account) - - return None - def get_by_uuid(self, pe_uuid: UUIDStr) -> PayoutEvent: res = self.pg_config.execute_sql_query( query=""" @@ -316,7 +301,7 @@ class UserPayoutEventManager(PayoutEventManager): request_data=request_data or {}, order_data=order_data, ) - d = payout_event.model_dump_mysql() + d = payout_event.model_dump_postgres() with self.pg_config.make_connection() as conn: with conn.cursor() as c: @@ -593,54 +578,18 @@ class BrokerageProductPayoutEventManager(PayoutEventManager): self.update(payout_event=bp_pe, status=PayoutStatus.COMPLETE) return bp_pe - return self._create_tx_bp_payout_from_payout_event( + return self.create_tx_bp_payout_from_payout_event( thl_ledger_manager=thl_ledger_manager, bp_pe=bp_pe, product=product, - skip_one_per_day_check=True, - skip_wallet_balance_check=True, ) - def create_pending_bp_payout_events( - self, - product: Product, - amount: USDCent, - payout_type: PayoutType = PayoutType.ACH, - ext_ref_id: str | None = None, - created: AwareDatetime | None = None, - ): - pass - - # def create_bp_payout_event( - # self, - # thl_ledger_manager: ThlLedgerManager, - # product: Product, - # amount: USDCent, - # payout_type: PayoutType = PayoutType.ACH, - # ext_ref_id: str | None = None, - # created: AwareDatetime | None = None, - # skip_wallet_balance_check: bool = False, - # skip_one_per_day_check: bool = False, - # ) -> BrokerageProductPayoutEvent: - # - # return self._create_tx_bp_payout_from_payout_event( - # thl_ledger_manager=thl_ledger_manager, - # bp_pe=bp_pe, - # product=product, - # amount=amount, - # created=created, - # skip_one_per_day_check=skip_one_per_day_check, - # skip_wallet_balance_check=skip_wallet_balance_check, - # ) - - def _create_tx_bp_payout_from_payout_event( + def create_tx_bp_payout_from_payout_event( self, thl_ledger_manager: ThlLedgerManager, bp_pe: BrokerageProductPayoutEvent, product: Product, created: AwareDatetime | None = None, - skip_wallet_balance_check: bool = False, - skip_one_per_day_check: bool = False, ) -> BrokerageProductPayoutEvent: """ This should not be called directly. @@ -655,22 +604,25 @@ class BrokerageProductPayoutEventManager(PayoutEventManager): amount=USDCent(bp_pe.amount), payoutevent_uuid=bp_pe.uuid, created=created, - skip_wallet_balance_check=skip_wallet_balance_check, - skip_one_per_day_check=skip_one_per_day_check, + skip_wallet_balance_check=True, + skip_one_per_day_check=True, ) + except LedgerTransactionConditionFailedError as e: + if e.args[0] == "duplicate tag": + raise ValueError( + f"""Payout event already exists! {e} + You are trying to create a tx that already exists. We can't know + if this is a new payout event with the same ref id, or you're + trying to run the same one twice ... So not setting the existing + payout event to FAILED, b/c the existing one is not failed! + Doing nothing ... + """ + ) from e + self.update(payout_event=bp_pe, status=PayoutStatus.FAILED) + raise except Exception as e: - e.pe_uuid = bp_pe.uuid - if self.check_for_ledger_tx( - thl_ledger_manager=thl_ledger_manager, - payout_event=bp_pe, - ): - LOG.warning(f"Got exception {e} but ledger tx exists! Continuing ... ") - self.update(payout_event=bp_pe, status=PayoutStatus.COMPLETE) - return bp_pe - else: - LOG.warning(f"Got exception {e}. No ledger tx was created.") - self.update(payout_event=bp_pe, status=PayoutStatus.FAILED) - raise e + self.update(payout_event=bp_pe, status=PayoutStatus.FAILED) + raise self.update(payout_event=bp_pe, status=PayoutStatus.COMPLETE) return bp_pe @@ -701,6 +653,10 @@ class BrokerageProductPayoutEventManager(PayoutEventManager): class BusinessPayoutEventManager(PostgresManagerWithRedis): + def __init__(self, *arg, **kwargs): + super().__init__(*arg, **kwargs) + self.bp_pe_manager = BrokerageProductPayoutEventManager(*arg, **kwargs) + def get_by_ext_ref_id(self, ext_ref_id: str) -> BusinessPayoutEvent: res = self.pg_config.execute_sql_query( """ @@ -1145,7 +1101,7 @@ class BusinessPayoutEventManager(PostgresManagerWithRedis): amount=USDCent(item["issue_amount"]), ext_ref_id=transaction_id, product_id=product.uuid, - cashout_method_uuid=self.CASHOUT_METHOD_UUID, + cashout_method_uuid=self.bp_pe_manager.CASHOUT_METHOD_UUID, debit_account_uuid=wallet_lookup[product_id], ) ) @@ -1159,12 +1115,10 @@ class BusinessPayoutEventManager(PostgresManagerWithRedis): # from the BrokerageProductPayoutEvents for bp_pe in bpe.bp_payouts: product = product_lookup[bp_pe.product_id] - self._create_tx_bp_payout_from_payout_event( + self.bp_pe_manager.create_tx_bp_payout_from_payout_event( thl_ledger_manager=thl_lm, bp_pe=bp_pe, product=product, - skip_one_per_day_check=True, - skip_wallet_balance_check=True, ) self.update_business_payout_event(pk=bpe.id, status=PayoutStatus.COMPLETE) diff --git a/tests/managers/thl/test_payout.py b/tests/managers/thl/test_payout.py index 000f425..03cd441 100644 --- a/tests/managers/thl/test_payout.py +++ b/tests/managers/thl/test_payout.py @@ -11,6 +11,7 @@ import pandas as pd import pytest from generalresearch import pg_helper +from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager from generalresearch.models.thl.product import Product from generalresearch.models.thl.user import User from generalresearch.currency import USDCent @@ -106,82 +107,43 @@ class TestPayout: delete_ledger_db, create_main_accounts, ): - delete_ledger_db() - create_main_accounts() - from generalresearch.models.thl.ledger import LedgerAccount - - thl_lm.get_account_or_create_bp_wallet(product=product) - brokerage_product_payout_event_manager.set_account_lookup_table(thl_lm=thl_lm) - - with pytest.raises(expected_exception=LedgerTransactionConditionFailedError): - # wallet balance failure - brokerage_product_payout_event_manager.create_bp_payout_event( - thl_ledger_manager=thl_lm, - product=product, - amount=USDCent(100), - skip_wallet_balance_check=False, - skip_one_per_day_check=False, - ) + # create_bp_payout_event does not get called directly. We have tests + # for the ledger methods already + pass - # (we don't have a special method for this) Put money in the BP's account - amount_cents = 100 - cash_account: LedgerAccount = thl_lm.get_account_cash() - bp_wallet: LedgerAccount = thl_lm.get_account_or_create_bp_wallet( - product=product - ) - - entries = [ - LedgerEntry( - direction=Direction.DEBIT, - account_uuid=cash_account.uuid, - amount=amount_cents, - ), - LedgerEntry( - direction=Direction.CREDIT, - account_uuid=bp_wallet.uuid, - amount=amount_cents, - ), - ] - - lm.create_tx(entries=entries) - assert 100 == lm.get_account_balance(account=bp_wallet) - - # Then run it again for $1.00 - brokerage_product_payout_event_manager.create_bp_payout_event( - thl_ledger_manager=thl_lm, - product=product, - amount=USDCent(100), - skip_wallet_balance_check=False, - skip_one_per_day_check=False, - ) - assert 0 == lm.get_account_balance(account=bp_wallet) - - # Run again should without balance check, should still fail due to day check - with pytest.raises(LedgerTransactionConditionFailedError): - brokerage_product_payout_event_manager.create_bp_payout_event( - thl_ledger_manager=thl_lm, - product=product, - amount=USDCent(100), - skip_wallet_balance_check=True, - skip_one_per_day_check=False, - ) - - # And then we can run again skip both checks - pe = brokerage_product_payout_event_manager.create_bp_payout_event( - thl_ledger_manager=thl_lm, - product=product, + @pytest.fixture + def pending_bp_pe( + self, + thl_web_rw, + product, + thl_lm: ThlLedgerManager, + brokerage_product_payout_event_manager, + utc_now + ) -> BrokerageProductPayoutEvent: + account = thl_lm.get_account_or_create_bp_wallet(product=product) + bp_pe = BrokerageProductPayoutEvent( + product_id=product.uuid, amount=USDCent(100), - skip_wallet_balance_check=True, - skip_one_per_day_check=True, - ) - assert -100 == lm.get_account_balance(account=bp_wallet) - - pe = brokerage_product_payout_event_manager.get_by_uuid(pe.uuid) - txs = lm.get_tx_filtered_by_metadata( - metadata_key="event_payout", metadata_value=pe.uuid - ) - - assert 1 == len(txs) + payout_type=PayoutType.ACH, + debit_account_uuid=account.uuid, + cashout_method_uuid=brokerage_product_payout_event_manager.CASHOUT_METHOD_UUID, + created=utc_now + ) + params = bp_pe.model_dump_postgres() + # This shouldn't exist. For testing only, so no supplier_payout + params['supplier_payout_id'] = None + thl_web_rw.execute_write(""" + INSERT INTO event_payout ( + uuid, debit_account_uuid, created, cashout_method_uuid, + amount, status, ext_ref_id, payout_type, order_data, + request_data, supplier_payout_id + ) VALUES ( + %(uuid)s, %(debit_account_uuid)s, %(created)s, %(cashout_method_uuid)s, + %(amount)s, %(status)s, %(ext_ref_id)s, %(payout_type)s, %(order_data)s, + %(request_data)s, %(supplier_payout_id)s + ); + """, params) + return bp_pe def test_create_bp_payout_quick_dupe( self, @@ -193,26 +155,22 @@ class TestPayout: lm, utc_now, create_main_accounts, + pending_bp_pe, ): thl_lm.get_account_or_create_bp_wallet(product=product) - brokerage_product_payout_event_manager.set_account_lookup_table(thl_lm=thl_lm) - brokerage_product_payout_event_manager.create_bp_payout_event( + brokerage_product_payout_event_manager.create_tx_bp_payout_from_payout_event( thl_ledger_manager=thl_lm, + bp_pe=pending_bp_pe, product=product, - amount=USDCent(100), - skip_wallet_balance_check=True, - skip_one_per_day_check=True, created=utc_now, ) with pytest.raises(ValueError) as cm: - brokerage_product_payout_event_manager.create_bp_payout_event( + brokerage_product_payout_event_manager.create_tx_bp_payout_from_payout_event( thl_ledger_manager=thl_lm, product=product, - amount=USDCent(100), - skip_wallet_balance_check=True, - skip_one_per_day_check=True, + bp_pe=pending_bp_pe, created=utc_now, ) assert "Payout event already exists!" in str(cm.value) @@ -297,44 +255,7 @@ class TestPayout: class TestPayoutEventManager: - def test_set_account_lookup_table( - self, payout_event_manager, thl_redis_config, thl_lm, delete_ledger_db - ): - delete_ledger_db() - rc = thl_redis_config.create_redis_client() - rc.delete("pem:account_to_product") - rc.delete("pem:product_to_account") - N = 5 - - for idx in range(N): - thl_lm.get_account_or_create_bp_wallet_by_uuid(product_uuid=uuid4().hex) - - res = rc.hgetall(name="pem:account_to_product") - assert len(res.items()) == 0 - - res = rc.hgetall(name="pem:product_to_account") - assert len(res.items()) == 0 - - payout_event_manager.set_account_lookup_table( - thl_lm=thl_lm, - ) - - res = rc.hgetall(name="pem:account_to_product") - assert len(res.items()) == N - - res = rc.hgetall(name="pem:product_to_account") - assert len(res.items()) == N - - thl_lm.get_account_or_create_bp_wallet_by_uuid(product_uuid=uuid4().hex) - payout_event_manager.set_account_lookup_table( - thl_lm=thl_lm, - ) - - res = rc.hgetall(name="pem:account_to_product") - assert len(res.items()) == N + 1 - - res = rc.hgetall(name="pem:product_to_account") - assert len(res.items()) == N + 1 + pass class TestBusinessPayoutEventManager: @@ -368,7 +289,6 @@ class TestBusinessPayoutEventManager: p1: Product = product_factory(business=business) thl_lm.get_account_or_create_bp_wallet(product=p1) - business_payout_event_manager.set_account_lookup_table(thl_lm=thl_lm) ach_id1 = uuid4().hex ach_id2 = uuid4().hex @@ -406,8 +326,6 @@ class TestBusinessPayoutEventManager: ) business.prebuild_payouts( - thl_pg_config=thl_web_rr, - thl_lm=thl_lm, bpem=business_payout_event_manager, ) @@ -1213,8 +1131,6 @@ class TestBusinessPayoutEventManager: pop_ledger=pop_ledger_merge, ) business.prebuild_payouts( - thl_pg_config=thl_web_rr, - thl_lm=thl_lm, bpem=business_payout_event_manager, ) |
