add "debit_card" as a new AccountType (#16)

This commit is contained in:
whaley
2026-09-08 20:03:13 +03:00
committed by GitHub
parent ae89311a8e
commit 12c3a7d79c
10 changed files with 69 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ import enum
class AccountType(str, enum.Enum):
CHECKING = "checking"
DEBIT_CARD = "debit_card"
SAVINGS = "savings"
CREDIT_CARD = "credit_card"
CASH = "cash"

View File

@@ -33,7 +33,7 @@ MAX_LOOKBACK_MONTHS = 24
# Accounts where a big, untouched balance means money isn't working — a
# credit card balance isn't "your cash", an investment account is already
# invested, and OTHER is too ambiguous to guess at.
_IDLE_CASH_ACCOUNT_TYPES = (AccountType.CHECKING, AccountType.SAVINGS, AccountType.CASH)
_IDLE_CASH_ACCOUNT_TYPES = (AccountType.CHECKING, AccountType.DEBIT_CARD, AccountType.SAVINGS, AccountType.CASH)
def _previous_month(year: int, month: int) -> tuple[int, int]:

View File

@@ -2,7 +2,7 @@
asset class.
Design note: "Cash" is not stored anywhere — it's derived live from
Account/Transaction data (checking/savings/cash/investment accounts) so the
Account/Transaction data (checking/debit-card/savings/cash/investment accounts) so the
Transactions feature stays the single source of truth for liquid money.
Investment accounts count here too: their balance is uninvested/unallocated
money sitting on the account, not the market value of what's actually
@@ -36,7 +36,13 @@ from app.schemas.net_worth import (
RiskLevelSummary,
)
CASH_ACCOUNT_TYPES = {AccountType.CHECKING, AccountType.SAVINGS, AccountType.CASH, AccountType.INVESTMENT}
CASH_ACCOUNT_TYPES = {
AccountType.CHECKING,
AccountType.DEBIT_CARD,
AccountType.SAVINGS,
AccountType.CASH,
AccountType.INVESTMENT,
}
RANGE_DAYS = {"30d": 30, "90d": 90, "1y": 365, "5y": 365 * 5}

View File

@@ -0,0 +1,34 @@
"""Account-type behavior shared by the accounts API and financial summaries."""
from datetime import date
from httpx import AsyncClient
from tests.helpers import money, txn_payload
async def test_debit_card_is_a_liquid_account_type(client: AsyncClient, categories):
response = await client.post(
"/accounts",
json={"name": "Debit card", "type": "debit_card", "currency": "RUB"},
)
assert response.status_code == 201
debit_card = response.json()
assert debit_card["type"] == "debit_card"
transaction = await client.post(
"/transactions",
json=txn_payload(
debit_card["id"],
type="income",
amount="1000.00",
category_id=categories["Salary"]["id"],
date=date.today().isoformat(),
),
)
assert transaction.status_code == 201
summary = await client.get("/net-worth/summary", params={"range": "all"})
assert summary.status_code == 200
assert money(summary.json()["current"]) == money("1000.00")

View File

@@ -51,6 +51,25 @@ async def test_idle_cash_ignores_investment_accounts(client: AsyncClient, catego
assert "idle_cash" not in await _alert_keys(client)
async def test_idle_cash_includes_debit_cards(client: AsyncClient, categories):
debit_card = await client.post(
"/accounts", json={"name": "Debit card", "type": "debit_card", "currency": "USD"}
)
debit_card_id = debit_card.json()["id"]
await client.post(
"/transactions",
json=_txn(
debit_card_id,
type="income",
amount="5000.00",
category_id=categories["Salary"]["id"],
date=STALE_DATE,
),
)
assert "idle_cash" in await _alert_keys(client)
async def test_idle_cash_respects_custom_threshold(client: AsyncClient, account_id, categories):
await client.post(
"/transactions",