From c975021688d2c195f8d585ebd919b9cbdacb09ad Mon Sep 17 00:00:00 2001 From: stuppie Date: Mon, 12 Oct 2026 12:58:37 -0700 Subject: Business common filter method. Remove payout attribute (should be called explicitly) --- generalresearch/managers/gr/business.py | 168 ++++++++++++++------------------ generalresearch/models/gr/business.py | 76 +++++++++------ pyproject.toml | 2 +- 3 files changed, 120 insertions(+), 126 deletions(-) diff --git a/generalresearch/managers/gr/business.py b/generalresearch/managers/gr/business.py index 734ba55..664c746 100644 --- a/generalresearch/managers/gr/business.py +++ b/generalresearch/managers/gr/business.py @@ -73,7 +73,7 @@ class BusinessBankAccountManager(PostgresManager): ba.id = ba_id return ba - def get_by_business_id(self, business_id: UUIDStr) -> list[BusinessBankAccount]: + def get_by_business_id(self, business_id: int) -> list[BusinessBankAccount]: with self.pg_config.make_connection() as conn, conn.cursor() as c: c.execute( @@ -239,7 +239,9 @@ class BusinessManager(PostgresManagerWithRedis): tm = TeamManager(pg_config=self.pg_config, redis_config=self.redis_config) tm.add_business(team=team, business=business) - return business + persisted_business = self.get_by_id(business_id=business_id) + assert persisted_business is not None + return persisted_business def get_all(self) -> list[Business]: """WARNING: This should be access by the /god/ page only, and only @@ -251,84 +253,86 @@ class BusinessManager(PostgresManagerWithRedis): :return: """ - from generalresearch.models.gr.business import Business + return self.filter() - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute(query=sql.SQL(""" - SELECT b.id, b.uuid, b.kind, b.name, b.tax_number - FROM common_business AS b - """)) - res = c.fetchall() + def filter( + self, + *, + business_id: PositiveInt | None = None, + business_uuid: UUIDStr | None = None, + team_id: PositiveInt | None = None, + user_id: PositiveInt | None = None, + ) -> list[Business]: + """Return businesses matching all of the supplied identifiers.""" + joins: list[sql.Composable] = [] + conditions: list[sql.Composable] = [] + params: list[int | str] = [] + + if business_id is not None: + conditions.append(sql.SQL("b.id = %s")) + params.append(business_id) + if business_uuid is not None: + conditions.append(sql.SQL("b.uuid = %s")) + params.append(business_uuid) + if team_id is not None or user_id is not None: + joins.append( + sql.SQL(""" + INNER JOIN common_team_businesses AS tb + ON tb.business_id = b.id + """) + ) + if team_id is not None: + conditions.append(sql.SQL("tb.team_id = %s")) + params.append(team_id) + if user_id is not None: + joins.append( + sql.SQL(""" + INNER JOIN common_membership AS m + ON m.team_id = tb.team_id + """) + ) + conditions.append(sql.SQL("m.user_id = %s")) + params.append(user_id) + + where = sql.SQL("") + if conditions: + where = sql.SQL("WHERE ") + sql.SQL(" AND ").join(conditions) - response = [] - for i in res: - # i["contact"] = BusinessContact.model_validate(i) - # i["address"] = BusinessAddress.model_validate(i) - i["contact"] = None - i["address"] = None + query = sql.SQL(""" + SELECT DISTINCT + b.id, + b.uuid, + b.kind, + b.name, + b.tax_number, + b.balance, + b.user_wallet_balance, + b.users_active_7d, + b.task_completes_7d, + b.balance_net_7d + FROM common_business AS b + {joins} + {where} + """).format(joins=sql.SQL(" ").join(joins), where=where) - response.append(Business.model_validate(i)) + with self.pg_config.make_connection() as conn, conn.cursor() as c: + c.execute(query=query, params=params) + res = c.fetchall() - return response + return [Business.model_validate(item) for item in res] def get_by_team( self, team_id: PositiveInt, ) -> list[Business]: - # conn: psycopg.Connection = GR_POSTGRES_C.make_connection() - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" - SELECT b.id, b.uuid, b.kind, b.name, b.tax_number - FROM common_business AS b - INNER JOIN common_team_businesses as tb - ON tb.business_id = b.id - WHERE tb.team_id = %s - """), - params=(team_id,), - ) - - res = c.fetchall() - - response = [] - - for i in res: - # i["contact"] = BusinessContact.model_validate(i) - # i["address"] = BusinessAddress.model_validate(i) - response.append(Business.model_validate(i)) - - return response + return self.filter(team_id=team_id) def get_by_user_id( self, user_id: PositiveInt, ) -> list[Business]: - from generalresearch.models.gr.business import Business - - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" - SELECT b.id, b.uuid, b.kind, b.name, b.tax_number - FROM common_business AS b - INNER JOIN common_team_businesses AS tb - ON tb.business_id = b.id - INNER JOIN common_membership AS m - ON m.team_id = tb.team_id - WHERE m.user_id = %s - """), - params=(user_id,), - ) - - res = c.fetchall() - - response = [] - for i in res: - # i["contact"] = BusinessContact.model_validate(i) - # i["address"] = BusinessAddress.model_validate(i) - response.append(Business.model_validate(i)) - - return response + return self.filter(user_id=user_id) def get_uuids(self, user_id: PositiveInt | None) -> list[UUIDStr]: """ @@ -367,48 +371,24 @@ class BusinessManager(PostgresManagerWithRedis): ) -> Business | None: assert UUID(hex=business_uuid).hex == business_uuid - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" - SELECT id, uuid, kind, name, tax_number - FROM common_business - WHERE uuid = %s - LIMIT 1; - """), - params=(business_uuid,), - ) - - res = c.fetchall() + res = self.filter(business_uuid=business_uuid) if len(res) == 0: return None assert len(res) == 1, "BusinessManager.get_by_uuid returned invalid results" - data = res[0] - # data["address"] = BusinessAddress.model_validate(data) - # data["contact"] = BusinessContact.model_validate(data) - return Business.model_validate(data) + return res[0] def get_by_id(self, business_id: PositiveInt) -> Business | None: assert isinstance(business_id, int) - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" - SELECT id, uuid, kind, name, tax_number - FROM common_business - WHERE id = %s - LIMIT 1; - """), - params=(business_id,), - ) - - res = c.fetchall() + res = self.filter(business_id=business_id) if len(res) == 0: return None - return Business.model_validate(res[0]) + assert len(res) == 1, "BusinessManager.get_by_id returned invalid results" + return res[0] def update_cached_fields(self, business: Business) -> None: """ diff --git a/generalresearch/models/gr/business.py b/generalresearch/models/gr/business.py index d5a4c0a..3deff19 100644 --- a/generalresearch/models/gr/business.py +++ b/generalresearch/models/gr/business.py @@ -13,7 +13,14 @@ import pyarrow as pa from dask.distributed import Client as DaskClient from psycopg.cursor import Cursor from psycopg.rows import dict_row -from pydantic import BaseModel, ConfigDict, Field, PositiveInt, ValidationError +from pydantic import ( + BaseModel, + ConfigDict, + Field, + PositiveInt, + ValidationError, + computed_field, +) from pydantic.json_schema import SkipJsonSchema from pydantic_extra_types.phone_numbers import PhoneNumber @@ -208,17 +215,16 @@ class Business(BaseModel): balance: BusinessBalances | None = Field(default=None, title="Business Balance") user_wallet_balance: BusinessUserWalletBalances | None = Field(default=None) - payouts_total_str: str | None = Field(default=None) - payouts_total: USDCent | None = Field(default=None) - payouts: list[BusinessPayoutEvent] | None = Field( - default=None, - title="Business Payouts", - description=( - "These are the ACH or Wire payments that were sent to the" - "Business as a single amount, summed for all the Business" - "child Products" - ), - ) + # These should be queried for if we want them + # payouts: list[BusinessPayoutEvent] | None = Field( + # default=None, + # title="Business Payouts", + # description=( + # "These are the ACH or Wire payments that were sent to the" + # "Business as a single amount, summed for all the Business" + # "child Products" + # ), + # ) pop_financial: list[POPFinancial] | None = Field(default=None) bp_accounts: list[LedgerAccount] | None = Field(default=None) @@ -243,7 +249,7 @@ class Business(BaseModel): f"Teams: {len(self.teams) if self.teams else 'Not Loaded'}\n" f"Bank Accounts: {len(self.bank_accounts) if self.bank_accounts else 'Not Loaded'}\n" f"–––\n" - f"Payouts: {len(self.payouts) if self.payouts else 'Not Loaded'}\n" + # f"Payouts: {len(self.payouts) if self.payouts else 'Not Loaded'}\n" f"Available Balance: {self.balance.available_balance if self.balance else 'Not Loaded'}\n" ) @@ -302,12 +308,12 @@ class Business(BaseModel): """ :return: All the Products for this Business """ - self.products = product_manager.filter_paginated(business_uuids=[self.uuid]) def prefetch_bank_accounts( self, business_bank_account_manager: BusinessBankAccountManager ) -> None: + assert self.id is not None self.bank_accounts = business_bank_account_manager.get_by_business_id( business_id=self.id ) @@ -510,22 +516,30 @@ class Business(BaseModel): self.task_completes_7d = res["task_completes_7d"] self.balance_net_7d = res["balance_net_7d"] - def prebuild_payouts( - self, - bpem: BusinessPayoutEventManager, - ) -> None: - LOG.debug(f"Business.prebuild_payouts({self.uuid=})") - - self.payouts = bpem.get_business_payout_events_for_business( - business_uuid=self.uuid, - order_by=OrderBy.DESC, - ) - self.prebuild_payouts_total() - - 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() + # def prebuild_payouts( + # self, + # bpem: BusinessPayoutEventManager, + # ) -> None: + # LOG.debug(f"Business.prebuild_payouts({self.uuid=})") + # + # self.payouts = bpem.get_business_payout_events_for_business( + # business_uuid=self.uuid, + # order_by=OrderBy.DESC, + # ) + # + # @computed_field + # @property + # def payouts_total(self) -> USDCent | None: + # if self.payouts is None: + # return None + # return USDCent(sum(payout.amount for payout in self.payouts)) + # + # @computed_field + # @property + # def payouts_total_str(self) -> str | None: + # if self.payouts_total is None: + # return None + # return self.payouts_total.to_usd_str() def prebuild_pop_financial( self, @@ -741,8 +755,8 @@ class Business(BaseModel): pop_ledger=pop_ledger, ) - self.prebuild_payouts(bpem=bpem) self.prebuild_metrics(product_manager=product_manager) + # self.prebuild_payouts(bpem=bpem) # self.prebuild_pop_financial( # product_manager=product_manager, # thl_lm=thl_lm, diff --git a/pyproject.toml b/pyproject.toml index 6b41fb1..d72ad8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "generalresearch" -version = "3.7.8" +version = "3.7.9" description = "Python Utilities for General Research" readme = "README.md" requires-python = ">=3.14" -- cgit v1.2.3