01 Refactor currency handling to store amounts in GTQ: Removed currency conversion utilities, updated models and API endpoints to directly handle GTQ amounts, and modified transaction processing logic for consistency. Enhanced frontend to reflect these changes, ensuring accurate display and submission of GTQ values across the application.
Refactor GTQ storage migration: Moved the conversion logic for centavo amounts to GTQ into a new migration function, m004_convert_to_gtq_storage, ensuring proper data type changes and updates across relevant tables. This enhances clarity and maintains the integrity of the migration process.
This commit is contained in:
parent
aa71321c84
commit
c83ebf43ab
8 changed files with 157 additions and 162 deletions
2
crud.py
2
crud.py
|
|
@ -267,7 +267,7 @@ async def get_client_balance_summary(client_id: str, as_of_time: Optional[dateti
|
|||
from loguru import logger
|
||||
# Verify timezone consistency for temporal filtering
|
||||
tz_info = "UTC" if as_of_time.tzinfo == timezone.utc else f"TZ: {as_of_time.tzinfo}"
|
||||
logger.info(f"Client {client_id[:8]}... balance as of {as_of_time} ({tz_info}): deposits.confirmed_at <= cutoff, payments.transaction_time <= cutoff → {total_deposits - total_payments} centavos remaining")
|
||||
logger.info(f"Client {client_id[:8]}... balance as of {as_of_time} ({tz_info}): deposits.confirmed_at <= cutoff, payments.transaction_time <= cutoff → {total_deposits - total_payments:.2f} GTQ remaining")
|
||||
|
||||
return ClientBalanceSummary(
|
||||
client_id=client_id,
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
# Currency conversion utilities for API boundary
|
||||
from decimal import Decimal
|
||||
from typing import Union
|
||||
|
||||
|
||||
def gtq_to_centavos(gtq_amount: Union[float, int, str]) -> int:
|
||||
"""Convert GTQ to centavos for database storage"""
|
||||
return int(Decimal(str(gtq_amount)) * 100)
|
||||
|
||||
|
||||
def centavos_to_gtq(centavos: int) -> float:
|
||||
"""Convert centavos to GTQ for API responses"""
|
||||
return float(centavos) / 100
|
||||
|
||||
|
||||
def format_gtq_currency(centavos: int) -> str:
|
||||
"""Format centavos as GTQ currency string"""
|
||||
gtq_amount = centavos_to_gtq(centavos)
|
||||
return f"Q{gtq_amount:.2f}"
|
||||
|
||||
|
||||
# Conversion helpers for API responses
|
||||
def deposit_db_to_api(deposit_db) -> dict:
|
||||
"""Convert database deposit model to API response"""
|
||||
return {
|
||||
"id": deposit_db.id,
|
||||
"client_id": deposit_db.client_id,
|
||||
"amount_gtq": centavos_to_gtq(deposit_db.amount),
|
||||
"currency": deposit_db.currency,
|
||||
"status": deposit_db.status,
|
||||
"notes": deposit_db.notes,
|
||||
"created_at": deposit_db.created_at,
|
||||
"confirmed_at": deposit_db.confirmed_at
|
||||
}
|
||||
|
||||
|
||||
def balance_summary_db_to_api(balance_db) -> dict:
|
||||
"""Convert database balance summary to API response"""
|
||||
return {
|
||||
"client_id": balance_db.client_id,
|
||||
"total_deposits_gtq": centavos_to_gtq(balance_db.total_deposits),
|
||||
"total_payments_gtq": centavos_to_gtq(balance_db.total_payments),
|
||||
"remaining_balance_gtq": centavos_to_gtq(balance_db.remaining_balance),
|
||||
"currency": balance_db.currency
|
||||
}
|
||||
|
|
@ -134,4 +134,88 @@ async def m003_add_max_daily_limit_config(db):
|
|||
ALTER TABLE satoshimachine.lamassu_config
|
||||
ADD COLUMN max_daily_limit_gtq INTEGER NOT NULL DEFAULT 2000
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m004_convert_to_gtq_storage(db):
|
||||
"""
|
||||
Convert centavo storage to GTQ storage by changing data types and converting existing data.
|
||||
"""
|
||||
# Convert dca_deposits amounts from centavos to GTQ
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.dca_deposits
|
||||
SET amount = CAST(amount AS DECIMAL(10,2)) / 100.0
|
||||
WHERE currency = 'GTQ'
|
||||
"""
|
||||
)
|
||||
|
||||
# Convert dca_payments amounts from centavos to GTQ
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.dca_payments
|
||||
SET amount_fiat = CAST(amount_fiat AS DECIMAL(10,2)) / 100.0
|
||||
"""
|
||||
)
|
||||
|
||||
# Convert lamassu_transactions amounts from centavos to GTQ
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.lamassu_transactions
|
||||
SET fiat_amount = CAST(fiat_amount AS DECIMAL(10,2)) / 100.0
|
||||
"""
|
||||
)
|
||||
|
||||
# Convert fixed_mode_daily_limit from centavos to GTQ
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.dca_clients
|
||||
SET fixed_mode_daily_limit = CAST(fixed_mode_daily_limit AS DECIMAL(10,2)) / 100.0
|
||||
WHERE fixed_mode_daily_limit IS NOT NULL
|
||||
"""
|
||||
)
|
||||
|
||||
# Convert max_daily_limit_gtq in config (if already in centavos)
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE satoshimachine.lamassu_config
|
||||
SET max_daily_limit_gtq = CAST(max_daily_limit_gtq AS DECIMAL(10,2)) / 100.0
|
||||
WHERE max_daily_limit_gtq > 1000
|
||||
"""
|
||||
)
|
||||
|
||||
# Change column types to DECIMAL
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE satoshimachine.dca_deposits
|
||||
ALTER COLUMN amount TYPE DECIMAL(10,2)
|
||||
"""
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE satoshimachine.dca_payments
|
||||
ALTER COLUMN amount_fiat TYPE DECIMAL(10,2)
|
||||
"""
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE satoshimachine.lamassu_transactions
|
||||
ALTER COLUMN fiat_amount TYPE DECIMAL(10,2)
|
||||
"""
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE satoshimachine.dca_clients
|
||||
ALTER COLUMN fixed_mode_daily_limit TYPE DECIMAL(10,2)
|
||||
"""
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
ALTER TABLE satoshimachine.lamassu_config
|
||||
ALTER COLUMN max_daily_limit_gtq TYPE DECIMAL(10,2)
|
||||
"""
|
||||
)
|
||||
82
models.py
82
models.py
|
|
@ -3,7 +3,7 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
|
||||
# DCA Client Models
|
||||
|
|
@ -12,7 +12,7 @@ class CreateDcaClientData(BaseModel):
|
|||
wallet_id: str
|
||||
username: str
|
||||
dca_mode: str = "flow" # 'flow' or 'fixed'
|
||||
fixed_mode_daily_limit: Optional[int] = None
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
|
||||
|
||||
class DcaClient(BaseModel):
|
||||
|
|
@ -30,45 +30,29 @@ class DcaClient(BaseModel):
|
|||
class UpdateDcaClientData(BaseModel):
|
||||
username: Optional[str] = None
|
||||
dca_mode: Optional[str] = None
|
||||
fixed_mode_daily_limit: Optional[int] = None
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
# API Models for Deposits (Frontend <-> Backend communication)
|
||||
class CreateDepositAPI(BaseModel):
|
||||
"""API model - frontend sends GTQ amounts"""
|
||||
client_id: str
|
||||
amount_gtq: float # Amount in GTQ (e.g., 150.75)
|
||||
currency: str = "GTQ"
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class DepositAPI(BaseModel):
|
||||
"""API model - backend returns GTQ amounts"""
|
||||
id: str
|
||||
client_id: str
|
||||
amount_gtq: float # Amount in GTQ (e.g., 150.75)
|
||||
currency: str
|
||||
status: str # 'pending' or 'confirmed'
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
confirmed_at: Optional[datetime]
|
||||
|
||||
|
||||
# Database Models for Deposits (Internal storage in centavos)
|
||||
# Deposit Models (Now storing GTQ directly)
|
||||
class CreateDepositData(BaseModel):
|
||||
"""Internal model - database stores centavos"""
|
||||
client_id: str
|
||||
amount: int # Amount in smallest currency unit (centavos for GTQ)
|
||||
amount: float # Amount in GTQ (e.g., 150.75)
|
||||
currency: str = "GTQ"
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator('amount')
|
||||
def round_amount_to_cents(cls, v):
|
||||
"""Ensure amount is rounded to 2 decimal places for DECIMAL(10,2) storage"""
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
return v
|
||||
|
||||
|
||||
class DcaDeposit(BaseModel):
|
||||
"""Internal model - database stores centavos"""
|
||||
id: str
|
||||
client_id: str
|
||||
amount: int
|
||||
amount: float # Amount in GTQ (e.g., 150.75)
|
||||
currency: str
|
||||
status: str # 'pending' or 'confirmed'
|
||||
notes: Optional[str]
|
||||
|
|
@ -85,7 +69,7 @@ class UpdateDepositStatusData(BaseModel):
|
|||
class CreateDcaPaymentData(BaseModel):
|
||||
client_id: str
|
||||
amount_sats: int
|
||||
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
exchange_rate: float
|
||||
transaction_type: str # 'flow', 'fixed', 'manual', 'commission'
|
||||
lamassu_transaction_id: Optional[str] = None
|
||||
|
|
@ -97,7 +81,7 @@ class DcaPayment(BaseModel):
|
|||
id: str
|
||||
client_id: str
|
||||
amount_sats: int
|
||||
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
exchange_rate: float
|
||||
transaction_type: str
|
||||
lamassu_transaction_id: Optional[str]
|
||||
|
|
@ -107,30 +91,19 @@ class DcaPayment(BaseModel):
|
|||
transaction_time: Optional[datetime] = None # Original ATM transaction time
|
||||
|
||||
|
||||
# API Models for Client Balance Summary
|
||||
class ClientBalanceSummaryAPI(BaseModel):
|
||||
"""API model - returns GTQ amounts"""
|
||||
client_id: str
|
||||
total_deposits_gtq: float # Total confirmed deposits in GTQ
|
||||
total_payments_gtq: float # Total payments made in GTQ
|
||||
remaining_balance_gtq: float # Available balance for DCA in GTQ
|
||||
currency: str
|
||||
|
||||
|
||||
# Internal Models for Client Balance Summary
|
||||
# Client Balance Summary (Now storing GTQ directly)
|
||||
class ClientBalanceSummary(BaseModel):
|
||||
"""Internal model - stores centavos"""
|
||||
client_id: str
|
||||
total_deposits: int # Total confirmed deposits
|
||||
total_payments: int # Total payments made
|
||||
remaining_balance: int # Available balance for DCA
|
||||
total_deposits: float # Total confirmed deposits in GTQ
|
||||
total_payments: float # Total payments made in GTQ
|
||||
remaining_balance: float # Available balance for DCA in GTQ
|
||||
currency: str
|
||||
|
||||
|
||||
# Transaction Processing Models
|
||||
class LamassuTransaction(BaseModel):
|
||||
transaction_id: str
|
||||
amount_fiat: int # Stored in centavos (GTQ * 100) for precision
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
amount_crypto: int
|
||||
exchange_rate: float
|
||||
transaction_type: str # 'cash_in' or 'cash_out'
|
||||
|
|
@ -141,7 +114,7 @@ class LamassuTransaction(BaseModel):
|
|||
# Lamassu Transaction Storage Models
|
||||
class CreateLamassuTransactionData(BaseModel):
|
||||
lamassu_transaction_id: str
|
||||
fiat_amount: int # Stored in centavos (GTQ * 100) for precision
|
||||
fiat_amount: float # Amount in GTQ (e.g., 150.75)
|
||||
crypto_amount: int
|
||||
commission_percentage: float
|
||||
discount: float = 0.0
|
||||
|
|
@ -158,7 +131,7 @@ class CreateLamassuTransactionData(BaseModel):
|
|||
class StoredLamassuTransaction(BaseModel):
|
||||
id: str
|
||||
lamassu_transaction_id: str
|
||||
fiat_amount: int
|
||||
fiat_amount: float # Amount in GTQ (e.g., 150.75)
|
||||
crypto_amount: int
|
||||
commission_percentage: float
|
||||
discount: float
|
||||
|
|
@ -194,7 +167,14 @@ class CreateLamassuConfigData(BaseModel):
|
|||
ssh_password: Optional[str] = None
|
||||
ssh_private_key: Optional[str] = None # Path to private key file or key content
|
||||
# DCA Client Limits
|
||||
max_daily_limit_gtq: int = 2000 # Maximum daily limit for Fixed mode clients
|
||||
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
|
||||
|
||||
@validator('max_daily_limit_gtq')
|
||||
def round_max_daily_limit(cls, v):
|
||||
"""Ensure max daily limit is rounded to 2 decimal places"""
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
return v
|
||||
|
||||
|
||||
class LamassuConfig(BaseModel):
|
||||
|
|
@ -224,7 +204,7 @@ class LamassuConfig(BaseModel):
|
|||
last_poll_time: Optional[datetime] = None
|
||||
last_successful_poll: Optional[datetime] = None
|
||||
# DCA Client Limits
|
||||
max_daily_limit_gtq: int = 2000 # Maximum daily limit for Fixed mode clients
|
||||
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
|
||||
|
||||
|
||||
class UpdateLamassuConfigData(BaseModel):
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ window.app = Vue.createApp({
|
|||
depositsTable: {
|
||||
columns: [
|
||||
{ name: 'client_id', align: 'left', label: 'Client', field: 'client_id' },
|
||||
{ name: 'amount_gtq', align: 'left', label: 'Amount', field: 'amount_gtq' },
|
||||
{ name: 'amount', align: 'left', label: 'Amount', field: 'amount' },
|
||||
{ name: 'currency', align: 'left', label: 'Currency', field: 'currency' },
|
||||
{ name: 'status', align: 'left', label: 'Status', field: 'status' },
|
||||
{ name: 'created_at', align: 'left', label: 'Created', field: 'created_at' },
|
||||
|
|
@ -130,11 +130,11 @@ window.app = Vue.createApp({
|
|||
///////////////////////////////////////////////////
|
||||
|
||||
methods: {
|
||||
// Utility Methods - Simplified since API handles conversion
|
||||
// Utility Methods
|
||||
formatCurrency(amount) {
|
||||
if (!amount) return 'Q 0.00';
|
||||
|
||||
// Amount is already in GTQ from API
|
||||
// Amount is now stored as GTQ directly in database
|
||||
return new Intl.NumberFormat('es-GT', {
|
||||
style: 'currency',
|
||||
currency: 'GTQ',
|
||||
|
|
@ -279,7 +279,7 @@ window.app = Vue.createApp({
|
|||
)
|
||||
return {
|
||||
...client,
|
||||
remaining_balance: balance.remaining_balance_gtq
|
||||
remaining_balance: balance.remaining_balance
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching balance for client ${client.id}:`, error)
|
||||
|
|
@ -303,7 +303,7 @@ window.app = Vue.createApp({
|
|||
try {
|
||||
const data = {
|
||||
client_id: this.quickDepositForm.selectedClient?.value,
|
||||
amount_gtq: this.quickDepositForm.amount, // Send GTQ directly - API handles conversion
|
||||
amount: this.quickDepositForm.amount, // Send GTQ directly - now stored as GTQ
|
||||
currency: 'GTQ',
|
||||
notes: this.quickDepositForm.notes
|
||||
}
|
||||
|
|
@ -376,7 +376,7 @@ window.app = Vue.createApp({
|
|||
try {
|
||||
const data = {
|
||||
client_id: this.depositFormDialog.data.client_id,
|
||||
amount_gtq: this.depositFormDialog.data.amount, // Send GTQ directly - API handles conversion
|
||||
amount: this.depositFormDialog.data.amount, // Send GTQ directly - now stored as GTQ
|
||||
currency: this.depositFormDialog.data.currency,
|
||||
notes: this.depositFormDialog.data.notes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@
|
|||
<q-tr :props="props">
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<div v-if="col.field == 'client_id'">${ getClientUsername(col.value) }</div>
|
||||
<div v-else-if="col.field == 'amount_gtq'">${ formatCurrency(col.value) }</div>
|
||||
<div v-else-if="col.field == 'amount'">${ formatCurrency(col.value) }</div>
|
||||
<div v-else-if="col.field == 'status'">
|
||||
<q-badge :color="col.value === 'confirmed' ? 'green' : 'orange'">
|
||||
${ col.value }
|
||||
|
|
@ -470,9 +470,9 @@
|
|||
<q-item-section>
|
||||
<q-item-label caption>Balance Summary</q-item-label>
|
||||
<q-item-label v-if="clientDetailsDialog.balance">
|
||||
Deposits: ${ formatCurrency(clientDetailsDialog.balance.total_deposits_gtq) } |
|
||||
Payments: ${ formatCurrency(clientDetailsDialog.balance.total_payments_gtq) } |
|
||||
Remaining: ${ formatCurrency(clientDetailsDialog.balance.remaining_balance_gtq) }
|
||||
Deposits: ${ formatCurrency(clientDetailsDialog.balance.total_deposits) } |
|
||||
Payments: ${ formatCurrency(clientDetailsDialog.balance.total_payments) } |
|
||||
Remaining: ${ formatCurrency(clientDetailsDialog.balance.remaining_balance) }
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ class LamassuTransactionProcessor:
|
|||
# Since crypto_atoms already includes commission, we need to extract the base amount
|
||||
# Formula: crypto_atoms = base_amount * (1 + effective_commission)
|
||||
# Therefore: base_amount = crypto_atoms / (1 + effective_commission)
|
||||
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
|
||||
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
|
||||
commission_amount_sats = crypto_atoms - base_crypto_atoms
|
||||
else:
|
||||
effective_commission = 0.0
|
||||
|
|
@ -755,9 +755,8 @@ class LamassuTransactionProcessor:
|
|||
client_sats_amount = calc['allocated_sats']
|
||||
proportion = calc['proportion']
|
||||
|
||||
# Calculate equivalent fiat value in centavos for tracking purposes (industry standard)
|
||||
# Store as centavos to maintain precision and avoid floating-point errors
|
||||
client_fiat_amount = round(client_sats_amount * 100 / exchange_rate) if exchange_rate > 0 else 0
|
||||
# Calculate equivalent fiat value in GTQ for tracking purposes
|
||||
client_fiat_amount = round(client_sats_amount / exchange_rate, 2) if exchange_rate > 0 else 0.0
|
||||
|
||||
distributions[client_id] = {
|
||||
"fiat_amount": client_fiat_amount,
|
||||
|
|
@ -765,7 +764,7 @@ class LamassuTransactionProcessor:
|
|||
"exchange_rate": exchange_rate
|
||||
}
|
||||
|
||||
logger.info(f"Client {client_id[:8]}... gets {client_sats_amount} sats (≈{client_fiat_amount/100:.2f} GTQ, {proportion:.2%} share)")
|
||||
logger.info(f"Client {client_id[:8]}... gets {client_sats_amount} sats (≈{client_fiat_amount:.2f} GTQ, {proportion:.2%} share)")
|
||||
|
||||
# Verification: ensure total distribution equals base amount
|
||||
total_distributed = sum(dist["sats_amount"] for dist in distributions.values())
|
||||
|
|
@ -781,9 +780,9 @@ class LamassuTransactionProcessor:
|
|||
current_balance = await get_client_balance_summary(client_id)
|
||||
if current_balance.remaining_balance > 0:
|
||||
final_distributions[client_id] = distribution
|
||||
logger.info(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance} centavos - APPROVED for {distribution['sats_amount']} sats")
|
||||
logger.info(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance:.2f} GTQ - APPROVED for {distribution['sats_amount']} sats")
|
||||
else:
|
||||
logger.warning(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance} centavos - REJECTED (negative balance)")
|
||||
logger.warning(f"Client {client_id[:8]}... final balance check: {current_balance.remaining_balance:.2f} GTQ - REJECTED (negative balance)")
|
||||
|
||||
if len(final_distributions) != len(distributions):
|
||||
logger.warning(f"Rejected {len(distributions) - len(final_distributions)} clients due to negative balances during final check")
|
||||
|
|
@ -830,22 +829,22 @@ class LamassuTransactionProcessor:
|
|||
# Final safety check: Verify client still has positive balance before payment
|
||||
current_balance = await get_client_balance_summary(client_id)
|
||||
if current_balance.remaining_balance <= 0:
|
||||
logger.error(f"CRITICAL: Client {client_id[:8]}... has negative balance ({current_balance.remaining_balance} centavos) - REFUSING payment of {distribution['sats_amount']} sats")
|
||||
logger.error(f"CRITICAL: Client {client_id[:8]}... has negative balance ({current_balance.remaining_balance:.2f} GTQ) - REFUSING payment of {distribution['sats_amount']} sats")
|
||||
continue
|
||||
|
||||
# Verify balance is sufficient for this distribution
|
||||
fiat_equivalent = distribution["fiat_amount"] # Already in centavos
|
||||
fiat_equivalent = distribution["fiat_amount"] # Amount in GTQ
|
||||
if current_balance.remaining_balance < fiat_equivalent:
|
||||
logger.error(f"CRITICAL: Client {client_id[:8]}... insufficient balance ({current_balance.remaining_balance} < {fiat_equivalent} centavos) - REFUSING payment")
|
||||
logger.error(f"CRITICAL: Client {client_id[:8]}... insufficient balance ({current_balance.remaining_balance:.2f} < {fiat_equivalent:.2f} GTQ) - REFUSING payment")
|
||||
continue
|
||||
|
||||
logger.info(f"Client {client_id[:8]}... pre-payment balance check: {current_balance.remaining_balance} centavos - SUFFICIENT for {fiat_equivalent} centavos payment")
|
||||
logger.info(f"Client {client_id[:8]}... pre-payment balance check: {current_balance.remaining_balance:.2f} GTQ - SUFFICIENT for {fiat_equivalent:.2f} GTQ payment")
|
||||
|
||||
# Create DCA payment record
|
||||
payment_data = CreateDcaPaymentData(
|
||||
client_id=client_id,
|
||||
amount_sats=distribution["sats_amount"],
|
||||
amount_fiat=distribution["fiat_amount"], # Still store centavos in DB
|
||||
amount_fiat=distribution["fiat_amount"], # Amount in GTQ
|
||||
exchange_rate=distribution["exchange_rate"],
|
||||
transaction_type="flow",
|
||||
lamassu_transaction_id=transaction_id,
|
||||
|
|
@ -888,12 +887,9 @@ class LamassuTransactionProcessor:
|
|||
return False
|
||||
|
||||
# Create descriptive memo with DCA metrics
|
||||
fiat_amount_centavos = distribution.get("fiat_amount", 0)
|
||||
fiat_amount_gtq = distribution.get("fiat_amount", 0.0)
|
||||
exchange_rate = distribution.get("exchange_rate", 0)
|
||||
|
||||
# Convert centavos to GTQ for display
|
||||
fiat_amount_gtq = fiat_amount_centavos / 100
|
||||
|
||||
# Calculate cost basis (fiat per BTC)
|
||||
if exchange_rate > 0:
|
||||
# exchange_rate is sats per fiat unit, so convert to fiat per BTC
|
||||
|
|
@ -1011,7 +1007,7 @@ class LamassuTransactionProcessor:
|
|||
# Calculate commission metrics
|
||||
if commission_percentage > 0:
|
||||
effective_commission = commission_percentage * (100 - discount) / 100
|
||||
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
|
||||
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
|
||||
commission_amount_sats = crypto_atoms - base_crypto_atoms
|
||||
else:
|
||||
effective_commission = 0.0
|
||||
|
|
@ -1021,10 +1017,10 @@ class LamassuTransactionProcessor:
|
|||
# Calculate exchange rate
|
||||
exchange_rate = base_crypto_atoms / fiat_amount if fiat_amount > 0 else 0
|
||||
|
||||
# Create transaction data (store fiat_amount in centavos for consistency)
|
||||
# Create transaction data with GTQ amounts
|
||||
transaction_data = CreateLamassuTransactionData(
|
||||
lamassu_transaction_id=transaction["transaction_id"],
|
||||
fiat_amount=int(fiat_amount * 100), # Convert GTQ to centavos
|
||||
fiat_amount=round(fiat_amount, 2), # Store GTQ with 2 decimal places
|
||||
crypto_amount=crypto_atoms,
|
||||
commission_percentage=commission_percentage,
|
||||
discount=discount,
|
||||
|
|
@ -1137,7 +1133,7 @@ class LamassuTransactionProcessor:
|
|||
|
||||
if commission_percentage and commission_percentage > 0:
|
||||
effective_commission = commission_percentage * (100 - discount) / 100
|
||||
base_crypto_atoms = int(crypto_atoms / (1 + effective_commission))
|
||||
base_crypto_atoms = round(crypto_atoms / (1 + effective_commission))
|
||||
commission_amount_sats = crypto_atoms - base_crypto_atoms
|
||||
else:
|
||||
commission_amount_sats = 0
|
||||
|
|
|
|||
50
views_api.py
50
views_api.py
|
|
@ -38,23 +38,14 @@ from .models import (
|
|||
DcaClient,
|
||||
UpdateDcaClientData,
|
||||
CreateDepositData,
|
||||
CreateDepositAPI,
|
||||
DepositAPI,
|
||||
DcaDeposit,
|
||||
UpdateDepositStatusData,
|
||||
ClientBalanceSummary,
|
||||
ClientBalanceSummaryAPI,
|
||||
CreateLamassuConfigData,
|
||||
LamassuConfig,
|
||||
UpdateLamassuConfigData,
|
||||
StoredLamassuTransaction,
|
||||
)
|
||||
from .currency_utils import (
|
||||
gtq_to_centavos,
|
||||
centavos_to_gtq,
|
||||
deposit_db_to_api,
|
||||
balance_summary_db_to_api,
|
||||
)
|
||||
|
||||
satmachineadmin_api_router = APIRouter()
|
||||
|
||||
|
|
@ -96,7 +87,7 @@ async def api_get_dca_client(
|
|||
async def api_get_client_balance(
|
||||
client_id: str,
|
||||
wallet: WalletTypeInfo = Depends(check_super_user),
|
||||
) -> ClientBalanceSummaryAPI:
|
||||
) -> ClientBalanceSummary:
|
||||
"""Get client balance summary"""
|
||||
client = await get_dca_client(client_id)
|
||||
if not client:
|
||||
|
|
@ -104,8 +95,7 @@ async def api_get_client_balance(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
|
||||
)
|
||||
|
||||
balance_db = await get_client_balance_summary(client_id)
|
||||
return ClientBalanceSummaryAPI(**balance_summary_db_to_api(balance_db))
|
||||
return await get_client_balance_summary(client_id)
|
||||
|
||||
|
||||
# DCA Deposit Endpoints
|
||||
|
|
@ -114,31 +104,30 @@ async def api_get_client_balance(
|
|||
@satmachineadmin_api_router.get("/api/v1/dca/deposits")
|
||||
async def api_get_deposits(
|
||||
wallet: WalletTypeInfo = Depends(check_super_user),
|
||||
) -> list[DepositAPI]:
|
||||
) -> list[DcaDeposit]:
|
||||
"""Get all deposits"""
|
||||
deposits_db = await get_all_deposits()
|
||||
return [DepositAPI(**deposit_db_to_api(deposit)) for deposit in deposits_db]
|
||||
return await get_all_deposits()
|
||||
|
||||
|
||||
@satmachineadmin_api_router.get("/api/v1/dca/deposits/{deposit_id}")
|
||||
async def api_get_deposit(
|
||||
deposit_id: str,
|
||||
wallet: WalletTypeInfo = Depends(check_super_user),
|
||||
) -> DepositAPI:
|
||||
) -> DcaDeposit:
|
||||
"""Get a specific deposit"""
|
||||
deposit_db = await get_deposit(deposit_id)
|
||||
if not deposit_db:
|
||||
deposit = await get_deposit(deposit_id)
|
||||
if not deposit:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
|
||||
)
|
||||
return DepositAPI(**deposit_db_to_api(deposit_db))
|
||||
return deposit
|
||||
|
||||
|
||||
@satmachineadmin_api_router.post("/api/v1/dca/deposits", status_code=HTTPStatus.CREATED)
|
||||
async def api_create_deposit(
|
||||
data: CreateDepositAPI,
|
||||
data: CreateDepositData,
|
||||
user: User = Depends(check_super_user),
|
||||
) -> DepositAPI:
|
||||
) -> DcaDeposit:
|
||||
"""Create a new deposit"""
|
||||
# Verify client exists
|
||||
client = await get_dca_client(data.client_id)
|
||||
|
|
@ -147,16 +136,7 @@ async def api_create_deposit(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
|
||||
)
|
||||
|
||||
# Convert GTQ to centavos at API boundary
|
||||
deposit_data = CreateDepositData(
|
||||
client_id=data.client_id,
|
||||
amount=gtq_to_centavos(data.amount_gtq),
|
||||
currency=data.currency,
|
||||
notes=data.notes
|
||||
)
|
||||
|
||||
deposit_db = await create_deposit(deposit_data)
|
||||
return DepositAPI(**deposit_db_to_api(deposit_db))
|
||||
return await create_deposit(data)
|
||||
|
||||
|
||||
@satmachineadmin_api_router.put("/api/v1/dca/deposits/{deposit_id}/status")
|
||||
|
|
@ -164,7 +144,7 @@ async def api_update_deposit_status(
|
|||
deposit_id: str,
|
||||
data: UpdateDepositStatusData,
|
||||
user: User = Depends(check_super_user),
|
||||
) -> DepositAPI:
|
||||
) -> DcaDeposit:
|
||||
"""Update deposit status (e.g., confirm deposit)"""
|
||||
deposit = await get_deposit(deposit_id)
|
||||
if not deposit:
|
||||
|
|
@ -172,13 +152,13 @@ async def api_update_deposit_status(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
|
||||
)
|
||||
|
||||
updated_deposit_db = await update_deposit_status(deposit_id, data)
|
||||
if not updated_deposit_db:
|
||||
updated_deposit = await update_deposit_status(deposit_id, data)
|
||||
if not updated_deposit:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update deposit.",
|
||||
)
|
||||
return DepositAPI(**deposit_db_to_api(updated_deposit_db))
|
||||
return updated_deposit
|
||||
|
||||
|
||||
# Transaction Polling Endpoints
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue