aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--generalresearch/managers/thl/payout.py81
-rw-r--r--generalresearch/models/gr/business.py13
-rw-r--r--tests/managers/thl/test_payout.py51
3 files changed, 93 insertions, 52 deletions
diff --git a/generalresearch/managers/thl/payout.py b/generalresearch/managers/thl/payout.py
index 0e44926..4409a63 100644
--- a/generalresearch/managers/thl/payout.py
+++ b/generalresearch/managers/thl/payout.py
@@ -699,7 +699,7 @@ class BrokerageProductPayoutEventManager(PayoutEventManager):
return self.get_bp_payout_events_for_accounts(accounts=accounts)
-class BusinessPayoutEventManager(BrokerageProductPayoutEventManager):
+class BusinessPayoutEventManager(PostgresManagerWithRedis):
def get_by_ext_ref_id(self, ext_ref_id: str) -> BusinessPayoutEvent:
res = self.pg_config.execute_sql_query(
@@ -735,6 +735,54 @@ class BusinessPayoutEventManager(BrokerageProductPayoutEventManager):
), "No BP payouts found for this Business Payout Event. This shouldn't happen!"
return bpe
+ def filter_by(
+ self,
+ business_uuids: Collection[UUIDStr] | None = None,
+ ) -> list[BusinessPayoutEvent]:
+
+ params = dict()
+ filters = []
+ if business_uuids is not None:
+ filters.append("business_id = ANY(%(business_uuids)s)")
+ params["business_uuids"] = business_uuids
+
+ assert len(filters) > 0, "must pass at least 1 filter"
+ filter_str = " AND ".join(filters)
+
+ res = self.pg_config.execute_sql_query(
+ f"""
+ SELECT
+ sp.*,
+ ep.bp_payouts
+ FROM supplier_payout sp
+ JOIN (
+ SELECT
+ ep_inner.supplier_payout_id,
+ jsonb_agg(
+ to_jsonb(ep_inner)
+ || jsonb_build_object('product_id', la.reference_uuid)
+ ORDER BY ep_inner.created
+ ) AS bp_payouts
+ FROM event_payout ep_inner
+ JOIN ledger_account la
+ ON ep_inner.debit_account_uuid = la.uuid
+ GROUP BY ep_inner.supplier_payout_id
+ ) ep ON sp.id = ep.supplier_payout_id
+ WHERE {filter_str}
+ """,
+ params,
+ )
+ bpes = []
+ for row in res:
+ for bp_payout in row["bp_payouts"]:
+ bp_payout["created"] = datetime.fromisoformat(bp_payout["created"])
+ bpe = BusinessPayoutEvent.model_validate(row)
+ assert (
+ bpe.bp_payouts is not None and len(bpe.bp_payouts) > 0
+ ), "No BP payouts found for this Business Payout Event. This shouldn't happen!"
+ bpes.append(bpe)
+ return bpes
+
def validate_business_payout_in_ledger(
self, ext_ref_id: str, thl_lm: ThlLedgerManager
):
@@ -807,34 +855,17 @@ class BusinessPayoutEventManager(BrokerageProductPayoutEventManager):
return None
- def get_business_payout_events_for_products(
+ def get_business_payout_events_for_business(
self,
- product_uuids: Collection[UUIDStr],
+ business_uuid: UUIDStr,
order_by: OrderBy | None = OrderBy.ASC,
) -> list[BusinessPayoutEvent]:
- res = self.get_bp_bp_payout_events_for_products(
- product_uuids=product_uuids,
- order_by=order_by,
+ order_by = order_by or OrderBy.ASC
+ bpes = self.filter_by(
+ business_uuids=[business_uuid],
)
-
- return self.from_bp_payout_events(bp_payout_events=res)
-
- @staticmethod
- def from_bp_payout_events(
- bp_payout_events: Collection[BrokerageProductPayoutEvent],
- ) -> list[BusinessPayoutEvent]:
- if len(bp_payout_events) == 0:
- return []
-
- grouped = defaultdict(list)
- for bp_pe in bp_payout_events:
- grouped[bp_pe.ext_ref_id].append(bp_pe)
-
- res = []
- for _, members in grouped.items():
- res.append(BusinessPayoutEvent.model_validate({"bp_payouts": members}))
-
- return res
+ bpes = sorted(bpes, key=lambda x: x.created, reverse=order_by == OrderBy.DESC)
+ return bpes
@staticmethod
def recoup_proportional(
diff --git a/generalresearch/models/gr/business.py b/generalresearch/models/gr/business.py
index bd66fbf..a842757 100644
--- a/generalresearch/models/gr/business.py
+++ b/generalresearch/models/gr/business.py
@@ -434,27 +434,22 @@ class Business(BaseModel):
def prebuild_payouts(
self,
- thl_pg_config: PostgresConfig,
- thl_lm: ThlLedgerManager,
bpem: BusinessPayoutEventManager,
) -> None:
LOG.debug(f"Business.prebuild_payouts({self.uuid=})")
- self.prefetch_products(thl_pg_config=thl_pg_config)
-
- self.payouts = bpem.get_business_payout_events_for_products(
- product_uuids=self.product_uuids,
+ self.payouts = bpem.get_business_payout_events_for_business(
+ business_uuid=self.uuid,
order_by=OrderBy.DESC,
)
-
self.prebuild_payouts_total()
+ return None
def prebuild_payouts_total(self):
assert self.payouts is not None
self.payouts_total = USDCent(sum([po.amount for po in self.payouts]))
self.payouts_total_str = self.payouts_total.to_usd_str()
-
- return
+ return None
def prebuild_pop_financial(
self,
diff --git a/tests/managers/thl/test_payout.py b/tests/managers/thl/test_payout.py
index d586ba2..000f425 100644
--- a/tests/managers/thl/test_payout.py
+++ b/tests/managers/thl/test_payout.py
@@ -9,6 +9,8 @@ import io
import pandas as pd
import pytest
+
+from generalresearch import pg_helper
from generalresearch.models.thl.product import Product
from generalresearch.models.thl.user import User
from generalresearch.currency import USDCent
@@ -865,26 +867,19 @@ class TestBusinessPayoutEventManager:
ach_id1 = uuid4().hex
ach_id2 = uuid4().hex
- # Product 1: Complete, Payout, Recon..
- s1 = session_with_tx_factory(
- user=u1,
- wall_req_cpi=Decimal("5.00"),
- started=start + timedelta(days=1),
- )
-
- # Product 2: Complete x10
- for idx in range(15):
+ # Product 1: Complete $10 x 20
+ for idx in range(20):
session_with_tx_factory(
user=u2,
- wall_req_cpi=Decimal("7.50"),
+ wall_req_cpi=Decimal("10.00"),
started=start + timedelta(days=1, hours=2, minutes=1 + idx),
)
- # Product 3: Complete x5
- for idx in range(10):
+ # Product 2: Complete $10 x 30
+ for idx in range(30):
session_with_tx_factory(
user=u3,
- wall_req_cpi=Decimal("7.50"),
+ wall_req_cpi=Decimal("10.00"),
started=start + timedelta(days=1, hours=3, minutes=1 + idx),
)
@@ -899,12 +894,12 @@ class TestBusinessPayoutEventManager:
)
bb = business.balance
- assert bb.payout == 25 * 712 + 475 # $7.50 * .95% = $7.125 = $7.12
- assert bb.net == (25 * 7.12 + 4.75) * 100
+ assert bb.payout == 475_00 # $500 * .95% = $475
+ assert bb.net == 475_00
bp1 = business_payout_event_manager.create_from_ach_or_wire(
business=business,
- amount=USDCent(bb.available_balance),
+ amount=USDCent(100_00),
pm=product_manager,
thl_lm=thl_lm,
created=start + timedelta(days=1, hours=5),
@@ -912,7 +907,19 @@ class TestBusinessPayoutEventManager:
)
print(f"{bp1=}")
assert isinstance(bp1, BusinessPayoutEvent)
- assert len(bp1.bp_payouts) == 3
+ assert len(bp1.bp_payouts) == 2
+
+ bp2 = business_payout_event_manager.create_from_ach_or_wire(
+ business=business,
+ amount=USDCent(bb.available_balance),
+ pm=product_manager,
+ thl_lm=thl_lm,
+ created=start + timedelta(days=2, hours=5),
+ transaction_id=ach_id2,
+ )
+ print(f"{bp2=}")
+ assert isinstance(bp2, BusinessPayoutEvent)
+ assert len(bp2.bp_payouts) == 2
with caplog.at_level(logging.WARNING):
business_payout_event_manager.resume_failed_business_payout(
@@ -920,7 +927,15 @@ class TestBusinessPayoutEventManager:
)
assert "Nothing to do!" in caplog.text
- bpe = business_payout_event_manager.get_by_ext_ref_id(ext_ref_id=ach_id1)
+ # bpe = business_payout_event_manager.get_by_ext_ref_id(ext_ref_id=ach_id1)
+ # bp_pe = bpe.bp_payouts[0]
+ # thl_web_rr.execute_write(
+ # """
+ # UPDATE event_payout
+ # SET status = %(status)s
+ # WHERE uuid = %(uuid)s""",
+ # {"uuid": bp_pe.uuid, "status": PayoutStatus.FAILED},
+ # )
assert 1 == 0
return None