|
1 | 1 | """CRUD operations for database models."""
|
2 | 2 |
|
3 | 3 | import uuid
|
| 4 | +from decimal import Decimal |
4 | 5 |
|
5 |
| -from sqlmodel import Session, select |
| 6 | +from sqlmodel import Session, desc, select |
6 | 7 |
|
7 | 8 | from app.core.security import get_password_hash, verify_password
|
8 |
| -from app.models import Item, ItemCreate, User, UserCreate, UserUpdate |
| 9 | +from app.models import ( |
| 10 | + CurrencyType, |
| 11 | + Item, |
| 12 | + ItemCreate, |
| 13 | + Transaction, |
| 14 | + TransactionCreate, |
| 15 | + TransactionType, |
| 16 | + User, |
| 17 | + UserCreate, |
| 18 | + UserUpdate, |
| 19 | + Wallet, |
| 20 | + WalletCreate, |
| 21 | +) |
9 | 22 |
|
10 | 23 |
|
11 | 24 | def create_user(*, session: Session, user_create: UserCreate) -> User:
|
@@ -57,3 +70,145 @@ def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -
|
57 | 70 | session.commit()
|
58 | 71 | session.refresh(db_item)
|
59 | 72 | return db_item
|
| 73 | + |
| 74 | + |
| 75 | +# Exchange rates for currency conversion (hardcoded as per requirements) |
| 76 | +EXCHANGE_RATES = { |
| 77 | + (CurrencyType.USD, CurrencyType.EUR): Decimal("0.85"), |
| 78 | + (CurrencyType.EUR, CurrencyType.USD): Decimal("1.18"), |
| 79 | + (CurrencyType.USD, CurrencyType.RUB): Decimal("75.0"), |
| 80 | + (CurrencyType.RUB, CurrencyType.USD): Decimal("0.013"), |
| 81 | + (CurrencyType.EUR, CurrencyType.RUB): Decimal("88.0"), |
| 82 | + (CurrencyType.RUB, CurrencyType.EUR): Decimal("0.011"), |
| 83 | +} |
| 84 | + |
| 85 | +# Transaction fee percentage |
| 86 | +TRANSACTION_FEE_RATE = Decimal("0.02") # 2% fee for cross-currency transactions |
| 87 | + |
| 88 | +# Wallet constraints |
| 89 | +MAX_WALLETS_PER_USER = 3 |
| 90 | + |
| 91 | +# Error messages |
| 92 | +WALLET_LIMIT_EXCEEDED = "User cannot have more than 3 wallets" |
| 93 | +WALLET_NOT_FOUND = "Wallet not found" |
| 94 | +INSUFFICIENT_BALANCE = "Insufficient balance for debit transaction" |
| 95 | + |
| 96 | + |
| 97 | +def create_wallet( |
| 98 | + *, |
| 99 | + session: Session, |
| 100 | + wallet_in: WalletCreate, |
| 101 | + user_id: uuid.UUID, |
| 102 | +) -> Wallet: |
| 103 | + """Create a new wallet for a user.""" |
| 104 | + # Check if user already has 3 wallets |
| 105 | + existing_wallets = session.exec( |
| 106 | + select(Wallet).where(Wallet.user_id == user_id), |
| 107 | + ).all() |
| 108 | + |
| 109 | + if len(existing_wallets) >= MAX_WALLETS_PER_USER: |
| 110 | + raise ValueError(WALLET_LIMIT_EXCEEDED) |
| 111 | + |
| 112 | + # Check if user already has a wallet with this currency |
| 113 | + for wallet in existing_wallets: |
| 114 | + if wallet.currency == wallet_in.currency: |
| 115 | + msg = f"User already has a {wallet_in.currency} wallet" |
| 116 | + raise ValueError(msg) |
| 117 | + |
| 118 | + db_wallet = Wallet.model_validate( |
| 119 | + wallet_in, |
| 120 | + update={"user_id": user_id, "balance": Decimal("0.00")}, |
| 121 | + ) |
| 122 | + session.add(db_wallet) |
| 123 | + session.commit() |
| 124 | + session.refresh(db_wallet) |
| 125 | + return db_wallet |
| 126 | + |
| 127 | + |
| 128 | +def get_wallet_by_id(*, session: Session, wallet_id: uuid.UUID) -> Wallet | None: |
| 129 | + """Get wallet by ID.""" |
| 130 | + return session.get(Wallet, wallet_id) |
| 131 | + |
| 132 | + |
| 133 | +def get_user_wallets(*, session: Session, user_id: uuid.UUID) -> list[Wallet]: |
| 134 | + """Get all wallets for a user.""" |
| 135 | + statement = select(Wallet).where(Wallet.user_id == user_id) |
| 136 | + return list(session.exec(statement).all()) |
| 137 | + |
| 138 | + |
| 139 | +def create_transaction( |
| 140 | + *, |
| 141 | + session: Session, |
| 142 | + transaction_in: TransactionCreate, |
| 143 | + wallet_id: uuid.UUID, |
| 144 | +) -> Transaction: |
| 145 | + """Create a new transaction for a wallet.""" |
| 146 | + # Get the wallet |
| 147 | + wallet = get_wallet_by_id(session=session, wallet_id=wallet_id) |
| 148 | + if not wallet: |
| 149 | + raise ValueError(WALLET_NOT_FOUND) |
| 150 | + |
| 151 | + # Determine transaction currency (default to wallet currency if not specified) |
| 152 | + transaction_currency = transaction_in.currency or wallet.currency |
| 153 | + amount = transaction_in.amount |
| 154 | + |
| 155 | + # Handle currency conversion and fees if needed |
| 156 | + if transaction_currency != wallet.currency: |
| 157 | + if (transaction_currency, wallet.currency) not in EXCHANGE_RATES: |
| 158 | + msg = ( |
| 159 | + f"Currency conversion from {transaction_currency} " |
| 160 | + f"to {wallet.currency} not supported" |
| 161 | + ) |
| 162 | + raise ValueError(msg) |
| 163 | + |
| 164 | + # Convert amount to wallet currency |
| 165 | + rate = EXCHANGE_RATES[(transaction_currency, wallet.currency)] |
| 166 | + amount = amount * rate |
| 167 | + |
| 168 | + # Apply transaction fee |
| 169 | + fee = amount * TRANSACTION_FEE_RATE |
| 170 | + amount = amount - fee |
| 171 | + |
| 172 | + # Check balance for debit transactions |
| 173 | + if transaction_in.transaction_type == TransactionType.DEBIT: |
| 174 | + if wallet.balance < amount: |
| 175 | + raise ValueError(INSUFFICIENT_BALANCE) |
| 176 | + new_balance = wallet.balance - amount |
| 177 | + else: # Credit transaction |
| 178 | + new_balance = wallet.balance + amount |
| 179 | + |
| 180 | + # Update wallet balance |
| 181 | + wallet.balance = new_balance.quantize(Decimal("0.01")) |
| 182 | + session.add(wallet) |
| 183 | + |
| 184 | + # Create transaction record |
| 185 | + db_transaction = Transaction.model_validate( |
| 186 | + transaction_in, |
| 187 | + update={ |
| 188 | + "wallet_id": wallet_id, |
| 189 | + "amount": amount.quantize(Decimal("0.01")), |
| 190 | + "currency": transaction_currency, |
| 191 | + }, |
| 192 | + ) |
| 193 | + session.add(db_transaction) |
| 194 | + session.commit() |
| 195 | + session.refresh(db_transaction) |
| 196 | + return db_transaction |
| 197 | + |
| 198 | + |
| 199 | +def get_wallet_transactions( |
| 200 | + *, |
| 201 | + session: Session, |
| 202 | + wallet_id: uuid.UUID, |
| 203 | + skip: int = 0, |
| 204 | + limit: int = 100, |
| 205 | +) -> list[Transaction]: |
| 206 | + """Get transactions for a wallet.""" |
| 207 | + statement = ( |
| 208 | + select(Transaction) |
| 209 | + .where(Transaction.wallet_id == wallet_id) |
| 210 | + .order_by(desc(Transaction.timestamp)) |
| 211 | + .offset(skip) |
| 212 | + .limit(limit) |
| 213 | + ) |
| 214 | + return list(session.exec(statement).all()) |
0 commit comments