diff options
| author | stuppie | 2026-09-09 13:53:57 -0600 |
|---|---|---|
| committer | stuppie | 2026-09-09 13:53:57 -0600 |
| commit | e278f65adc0cec52f354ab1dffaaf3507e49245b (patch) | |
| tree | f5c67078d215e801091272ea6d1644b924703d74 | |
| parent | 32f30b5ddc600e22e071f7659f1872a18ebf42c7 (diff) | |
| download | generalresearch-e278f65adc0cec52f354ab1dffaaf3507e49245b.tar.gz generalresearch-e278f65adc0cec52f354ab1dffaaf3507e49245b.zip | |
get user txs incuding UserLedgerTransactionAttemptCredit, query by wallet uuid or name
| -rw-r--r-- | generalresearch/managers/thl/ledger_manager/ledger.py | 18 | ||||
| -rw-r--r-- | generalresearch/managers/thl/ledger_manager/thl_ledger.py | 37 | ||||
| -rw-r--r-- | generalresearch/models/thl/ledger.py | 42 |
3 files changed, 83 insertions, 14 deletions
diff --git a/generalresearch/managers/thl/ledger_manager/ledger.py b/generalresearch/managers/thl/ledger_manager/ledger.py index 3a02cdf..d027bd5 100644 --- a/generalresearch/managers/thl/ledger_manager/ledger.py +++ b/generalresearch/managers/thl/ledger_manager/ledger.py @@ -869,6 +869,24 @@ class LedgerAccountManager(LedgerManagerBasePostgres): ) return res[0] if len(res) == 1 else None + def get_account_by_uuid( + self, account_uuid: UUIDStr, raise_on_error: bool = True + ) -> LedgerAccount | None: + check_valid_uuid(account_uuid) + res = self.pg_config.execute_sql_query( + query=""" + SELECT + uuid, display_name, qualified_name, account_type, + normal_balance, reference_type, reference_uuid, currency + FROM ledger_account + WHERE uuid = %s; + """, + params=[account_uuid], + ) + if raise_on_error and len(res) != 1: + raise LedgerAccountDoesntExistError + return LedgerAccount.model_validate(res[0]) if len(res) == 1 else None + def get_account_many_( self, qualified_names: list[str], raise_on_error: bool = True ) -> list[dict[str, Any]]: diff --git a/generalresearch/managers/thl/ledger_manager/thl_ledger.py b/generalresearch/managers/thl/ledger_manager/thl_ledger.py index 2fef50f..5210189 100644 --- a/generalresearch/managers/thl/ledger_manager/thl_ledger.py +++ b/generalresearch/managers/thl/ledger_manager/thl_ledger.py @@ -2234,12 +2234,39 @@ class ThlLedgerManager(LedgerManager): page: int = 1, size: int = 50, order_by: str | None = "created,tag", + account_uuid: UUIDStr | None = None, + qualified_name: str | None = None, ) -> UserLedgerTransactions: + """Return user-facing transactions for one of the user's ledger accounts. + + The account can be selected by UUID or qualified name. If neither is + supplied, this defaults to the user's primary wallet. + """ + assert user.product_id is not None user.prefetch_product(self.pg_config) - user_account = self.get_account_or_create_user_wallet(user) + assert account_uuid is None or qualified_name is None, ( + "Pass either account_uuid or qualified_name, not both" + ) + + if account_uuid is not None: + user_account = self.get_account_by_uuid(account_uuid) + elif qualified_name is not None: + user_account = self.get_account(qualified_name) + else: + user_account = self.get_account_or_create_user_wallet(user) + + assert user_account is not None + assert user_account.reference_type == "user", ( + "Account must be a user ledger account" + ) + assert user_account.reference_uuid == user.uuid, "Account must be owned by user" exclude_txs_before = None - if user.product_id == JAMES_BILLINGS_BPID: + if ( + user.product_id == JAMES_BILLINGS_BPID + and user_account.account_type == AccountType.USER_WALLET + and user_account.currency == self.currency.value + ): time_start = ( max([JAMES_BILLINGS_TX_CUTOFF, time_start]) if time_start is not None @@ -2263,7 +2290,11 @@ class ThlLedgerManager(LedgerManager): user_account=user_account, txs=txs, product_id=user.product_id, - payout_format=user.product.payout_config.payout_format, + payout_format=( + user.product.payout_config.payout_format + if user_account.currency == self.currency.value + else None + ), summary=summary, page=page, size=size, diff --git a/generalresearch/models/thl/ledger.py b/generalresearch/models/thl/ledger.py index 613d16e..4160e98 100644 --- a/generalresearch/models/thl/ledger.py +++ b/generalresearch/models/thl/ledger.py @@ -352,7 +352,7 @@ class LedgerTransaction(BaseModel): return d def to_user_tx( - self, user_account: LedgerAccount, product_id: str, payout_format: str + self, user_account: LedgerAccount, product_id: str, payout_format: str | None ): from generalresearch.models.thl.wallet.definitions import PayoutType @@ -386,10 +386,15 @@ class LedgerTransaction(BaseModel): raise ValueError(payout_type) return UserLedgerTransactionUserPayout.model_validate(d) elif d["tx_type"] == TransactionType.BP_PAYMENT.value: + assert len(debits) == 1 or len(credits) == 1 + d["amount"] = credits[0].amount if credits else debits[0].amount * -1 + d["tsid"] = self.metadata.get("thl_session") + return UserLedgerTransactionTaskComplete.model_validate(d) + elif d["tx_type"] == TransactionType.USER_ATTEMPT_CREDIT.value: assert len(credits) == 1 d["amount"] = credits[0].amount d["tsid"] = self.metadata.get("thl_session") - return UserLedgerTransactionTaskComplete.model_validate(d) + return UserLedgerTransactionAttemptCredit.model_validate(d) elif d["tx_type"] == TransactionType.USER_BONUS.value: assert len(credits) == 1 d["amount"] = credits[0].amount @@ -546,11 +551,6 @@ class UserLedgerTransactionTaskComplete(UserLedgerTransaction): def create_url(self, product_id: str) -> str | None: return f"https://fsb.generalresearch.com/{product_id}/status/{self.tsid}/" - @model_validator(mode="after") - def validate_amount(self): - assert self.amount >= 0, f"UserLedgerTransactionTaskComplete: {self.amount=}" - return self - class UserLedgerTransactionTaskAdjustment(UserLedgerTransaction): model_config = ConfigDict( @@ -578,11 +578,28 @@ class UserLedgerTransactionTaskAdjustment(UserLedgerTransaction): return f"https://fsb.generalresearch.com/{product_id}/status/{self.tsid}/" +class UserLedgerTransactionAttemptCredit(UserLedgerTransaction): + tx_type: Literal[TransactionType.USER_ATTEMPT_CREDIT] = Field( + default=TransactionType.USER_ATTEMPT_CREDIT + ) + description: str = Field(default="Attempt Credit", max_length=255) + tsid: UUIDStr + + def create_url(self, product_id: str) -> str | None: + return f"https://fsb.generalresearch.com/{product_id}/status/{self.tsid}/" + + @model_validator(mode="after") + def validate_amount(self): + assert self.amount > 0, f"UserLedgerTransactionAttemptCredit: {self.amount=}" + return self + + UserLedgerTransactionType = Annotated[ UserLedgerTransactionUserPayout | UserLedgerTransactionUserBonus | UserLedgerTransactionTaskAdjustment - | UserLedgerTransactionTaskComplete, + | UserLedgerTransactionTaskComplete + | UserLedgerTransactionAttemptCredit, Field(discriminator="tx_type"), ] @@ -611,6 +628,9 @@ class UserLedgerTransactionTypesSummary(BaseModel): user_bonus: UserLedgerTransactionTypeSummary = Field( default_factory=UserLedgerTransactionTypeSummary ) + user_attempt_credit: UserLedgerTransactionTypeSummary = Field( + default_factory=UserLedgerTransactionTypeSummary + ) user_payout_request: UserLedgerTransactionTypeSummary = Field( default_factory=UserLedgerTransactionTypeSummary ) @@ -623,8 +643,8 @@ class UserLedgerTransactions(Page): transactions: list[UserLedgerTransactionType] = Field(default_factory=list) # The summary is w.r.t an optional time-filter. The transactions are - # paginated so the counts won't necesarily match. In other words, the - # summary is across all transaction in all pages, not this the transactions + # paginated so the counts won't necessarily match. In other words, the + # summary is across all transactions in all pages, not this the transactions # in this page. summary: UserLedgerTransactionTypesSummary = Field() @@ -634,7 +654,7 @@ class UserLedgerTransactions(Page): user_account: LedgerAccount, txs: list[LedgerTransaction], product_id: str, - payout_format: str, + payout_format: PayoutFormatType | None, summary: UserLedgerTransactionTypesSummary, page: int, size: int, |
