feat: Enhance transaction model and dashboard with Azure OAuth integration
This commit is contained in:
+20
-5
@@ -13,7 +13,9 @@ from app.service.recon_service import (
|
||||
router = APIRouter(prefix="/api", tags=["Transactions"])
|
||||
|
||||
|
||||
@router.get("/transactions", response_model=list[Transaction], summary="List transactions")
|
||||
@router.get(
|
||||
"/transactions", response_model=list[Transaction], summary="List transactions"
|
||||
)
|
||||
async def list_transactions_endpoint(
|
||||
status: StatusType | None = Query(None, description="Filter by status"),
|
||||
flag: FlagType | None = Query(None, description="Filter by flag"),
|
||||
@@ -21,7 +23,11 @@ async def list_transactions_endpoint(
|
||||
return list_transactions(status=status, flag=flag)
|
||||
|
||||
|
||||
@router.get("/transactions/{transaction_id}", response_model=Transaction, summary="Get a transaction")
|
||||
@router.get(
|
||||
"/transactions/{transaction_id}",
|
||||
response_model=Transaction,
|
||||
summary="Get a transaction",
|
||||
)
|
||||
async def get_transaction_endpoint(transaction_id: str) -> Transaction:
|
||||
transaction = get_transaction_by_id(transaction_id)
|
||||
if transaction is None:
|
||||
@@ -29,15 +35,24 @@ async def get_transaction_endpoint(transaction_id: str) -> Transaction:
|
||||
return transaction
|
||||
|
||||
|
||||
@router.post("/transactions", response_model=Transaction, status_code=201, summary="Submit a transaction")
|
||||
@router.post(
|
||||
"/transactions",
|
||||
response_model=Transaction,
|
||||
status_code=201,
|
||||
summary="Submit a transaction",
|
||||
)
|
||||
async def create_transaction_endpoint(transaction: Transaction) -> Transaction:
|
||||
created = add_transaction(transaction)
|
||||
if not created:
|
||||
raise HTTPException(status_code=409, detail=f"{transaction.transaction_id} already exists")
|
||||
raise HTTPException(
|
||||
status_code=409, detail=f"{transaction.transaction_id} already exists"
|
||||
)
|
||||
return transaction
|
||||
|
||||
|
||||
@router.delete("/transactions/{transaction_id}", status_code=204, summary="Delete a transaction")
|
||||
@router.delete(
|
||||
"/transactions/{transaction_id}", status_code=204, summary="Delete a transaction"
|
||||
)
|
||||
async def delete_transaction_endpoint(transaction_id: str) -> None:
|
||||
removed = delete_transaction_by_id(transaction_id)
|
||||
if not removed:
|
||||
|
||||
@@ -11,14 +11,18 @@ load_dotenv(PROJECT_ROOT / ".env")
|
||||
|
||||
class Settings:
|
||||
def __init__(self) -> None:
|
||||
self.session_secret_key = getenv("SESSION_SECRET_KEY", "change-me-in-production")
|
||||
self.session_secret_key = getenv(
|
||||
"SESSION_SECRET_KEY", "change-me-in-production"
|
||||
)
|
||||
self.azure_tenant_id = getenv("AZURE_TENANT_ID")
|
||||
self.azure_client_id = getenv("AZURE_CLIENT_ID")
|
||||
self.azure_client_secret = getenv("AZURE_CLIENT_SECRET")
|
||||
|
||||
@property
|
||||
def azure_configured(self) -> bool:
|
||||
return bool(self.azure_tenant_id and self.azure_client_id and self.azure_client_secret)
|
||||
return bool(
|
||||
self.azure_tenant_id and self.azure_client_id and self.azure_client_secret
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
|
||||
@@ -8,4 +8,3 @@ app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ class Transaction(BaseModel):
|
||||
amount: float
|
||||
status: Literal["Matched", "Unmatched", "Pending"]
|
||||
flag: Literal["None", "Duplicate", "Threshold Breach", "Manual Review"]
|
||||
reference_id: str
|
||||
counterparty: str
|
||||
currency: str
|
||||
booking_date: date
|
||||
settlement_date: date
|
||||
description: str
|
||||
|
||||
|
||||
class ReconSummary(BaseModel):
|
||||
|
||||
@@ -15,6 +15,12 @@ TRANSACTIONS: list[Transaction] = [
|
||||
amount=12400.00,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0001",
|
||||
counterparty="Morgan Stanley",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 9),
|
||||
settlement_date=date(2026, 4, 12),
|
||||
description="FX Swap USD/EUR",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-002",
|
||||
@@ -23,6 +29,12 @@ TRANSACTIONS: list[Transaction] = [
|
||||
amount=3750.50,
|
||||
status="Unmatched",
|
||||
flag="Duplicate",
|
||||
reference_id="REF-2026-0002",
|
||||
counterparty="JPMorgan Chase",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 10),
|
||||
settlement_date=date(2026, 4, 13),
|
||||
description="Interest rate swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-003",
|
||||
@@ -31,6 +43,12 @@ TRANSACTIONS: list[Transaction] = [
|
||||
amount=88200.00,
|
||||
status="Unmatched",
|
||||
flag="Threshold Breach",
|
||||
reference_id="REF-2026-0003",
|
||||
counterparty="Goldman Sachs",
|
||||
currency="GBP",
|
||||
booking_date=date(2026, 4, 10),
|
||||
settlement_date=date(2026, 4, 14),
|
||||
description="Cross-currency swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-004",
|
||||
@@ -39,6 +57,12 @@ TRANSACTIONS: list[Transaction] = [
|
||||
amount=540.00,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0004",
|
||||
counterparty="Barclays",
|
||||
currency="EUR",
|
||||
booking_date=date(2026, 4, 11),
|
||||
settlement_date=date(2026, 4, 15),
|
||||
description="Bond settlement",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-005",
|
||||
@@ -47,11 +71,229 @@ TRANSACTIONS: list[Transaction] = [
|
||||
amount=21000.00,
|
||||
status="Pending",
|
||||
flag="Manual Review",
|
||||
reference_id="REF-2026-0005",
|
||||
counterparty="Deutsche Bank",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 12),
|
||||
settlement_date=date(2026, 4, 16),
|
||||
description="Equity options trade",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-006",
|
||||
date=date(2026, 4, 14),
|
||||
account="ACC-5542",
|
||||
amount=15600.00,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0006",
|
||||
counterparty="Credit Suisse",
|
||||
currency="CHF",
|
||||
booking_date=date(2026, 4, 13),
|
||||
settlement_date=date(2026, 4, 17),
|
||||
description="Repo transaction",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-007",
|
||||
date=date(2026, 4, 14),
|
||||
account="ACC-8834",
|
||||
amount=2250.75,
|
||||
status="Unmatched",
|
||||
flag="Threshold Breach",
|
||||
reference_id="REF-2026-0007",
|
||||
counterparty="Citigroup",
|
||||
currency="JPY",
|
||||
booking_date=date(2026, 4, 13),
|
||||
settlement_date=date(2026, 4, 18),
|
||||
description="Forward contract",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-008",
|
||||
date=date(2026, 4, 15),
|
||||
account="ACC-2201",
|
||||
amount=45000.00,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0008",
|
||||
counterparty="UBS",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 14),
|
||||
settlement_date=date(2026, 4, 19),
|
||||
description="Commodity swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-009",
|
||||
date=date(2026, 4, 15),
|
||||
account="ACC-7123",
|
||||
amount=8900.50,
|
||||
status="Pending",
|
||||
flag="Manual Review",
|
||||
reference_id="REF-2026-0009",
|
||||
counterparty="HSBC",
|
||||
currency="AUD",
|
||||
booking_date=date(2026, 4, 14),
|
||||
settlement_date=date(2026, 4, 20),
|
||||
description="Index futures",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-010",
|
||||
date=date(2026, 4, 16),
|
||||
account="ACC-3345",
|
||||
amount=1200.00,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0010",
|
||||
counterparty="Bank of America",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 15),
|
||||
settlement_date=date(2026, 4, 21),
|
||||
description="Interest rate cap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-011",
|
||||
date=date(2026, 4, 16),
|
||||
account="ACC-5501",
|
||||
amount=67500.00,
|
||||
status="Unmatched",
|
||||
flag="Duplicate",
|
||||
reference_id="REF-2026-0011",
|
||||
counterparty="Wells Fargo",
|
||||
currency="EUR",
|
||||
booking_date=date(2026, 4, 15),
|
||||
settlement_date=date(2026, 4, 22),
|
||||
description="Currency forward",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-012",
|
||||
date=date(2026, 4, 17),
|
||||
account="ACC-6789",
|
||||
amount=3400.25,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0012",
|
||||
counterparty="BNY Mellon",
|
||||
currency="GBP",
|
||||
booking_date=date(2026, 4, 16),
|
||||
settlement_date=date(2026, 4, 23),
|
||||
description="Bond purchase",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-013",
|
||||
date=date(2026, 4, 17),
|
||||
account="ACC-1234",
|
||||
amount=52100.00,
|
||||
status="Pending",
|
||||
flag="Threshold Breach",
|
||||
reference_id="REF-2026-0013",
|
||||
counterparty="State Street",
|
||||
currency="JPY",
|
||||
booking_date=date(2026, 4, 16),
|
||||
settlement_date=date(2026, 4, 24),
|
||||
description="Swaption settlement",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-014",
|
||||
date=date(2026, 4, 18),
|
||||
account="ACC-9876",
|
||||
amount=920.50,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0014",
|
||||
counterparty="Nomura",
|
||||
currency="CHF",
|
||||
booking_date=date(2026, 4, 17),
|
||||
settlement_date=date(2026, 4, 25),
|
||||
description="Basket trade",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-015",
|
||||
date=date(2026, 4, 18),
|
||||
account="ACC-4455",
|
||||
amount=29300.00,
|
||||
status="Unmatched",
|
||||
flag="Manual Review",
|
||||
reference_id="REF-2026-0015",
|
||||
counterparty="Mizuho",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 17),
|
||||
settlement_date=date(2026, 4, 26),
|
||||
description="Variance swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-016",
|
||||
date=date(2026, 4, 19),
|
||||
account="ACC-2288",
|
||||
amount=11500.75,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0016",
|
||||
counterparty="RBC",
|
||||
currency="CAD",
|
||||
booking_date=date(2026, 4, 18),
|
||||
settlement_date=date(2026, 4, 27),
|
||||
description="CDS contract",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-017",
|
||||
date=date(2026, 4, 19),
|
||||
account="ACC-3399",
|
||||
amount=76800.00,
|
||||
status="Unmatched",
|
||||
flag="Threshold Breach",
|
||||
reference_id="REF-2026-0017",
|
||||
counterparty="Scotiabank",
|
||||
currency="AUD",
|
||||
booking_date=date(2026, 4, 18),
|
||||
settlement_date=date(2026, 4, 28),
|
||||
description="Total return swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-018",
|
||||
date=date(2026, 4, 20),
|
||||
account="ACC-5566",
|
||||
amount=4100.25,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0018",
|
||||
counterparty="TD Bank",
|
||||
currency="USD",
|
||||
booking_date=date(2026, 4, 19),
|
||||
settlement_date=date(2026, 4, 29),
|
||||
description="Equity collar",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-019",
|
||||
date=date(2026, 4, 20),
|
||||
account="ACC-7788",
|
||||
amount=18600.00,
|
||||
status="Pending",
|
||||
flag="Manual Review",
|
||||
reference_id="REF-2026-0019",
|
||||
counterparty="MUFG",
|
||||
currency="EUR",
|
||||
booking_date=date(2026, 4, 19),
|
||||
settlement_date=date(2026, 4, 30),
|
||||
description="Volatility swap",
|
||||
),
|
||||
Transaction(
|
||||
transaction_id="TXN-020",
|
||||
date=date(2026, 4, 21),
|
||||
account="ACC-9900",
|
||||
amount=8250.50,
|
||||
status="Matched",
|
||||
flag="None",
|
||||
reference_id="REF-2026-0020",
|
||||
counterparty="Sumitomo Mitsui",
|
||||
currency="GBP",
|
||||
booking_date=date(2026, 4, 20),
|
||||
settlement_date=date(2026, 5, 1),
|
||||
description="Knock-out option",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def list_transactions(status: StatusType | None = None, flag: FlagType | None = None) -> list[Transaction]:
|
||||
def list_transactions(
|
||||
status: StatusType | None = None, flag: FlagType | None = None
|
||||
) -> list[Transaction]:
|
||||
results = TRANSACTIONS
|
||||
if status:
|
||||
results = [item for item in results if item.status == status]
|
||||
|
||||
+14
-6
@@ -12,27 +12,35 @@ project_root = Path(__file__).resolve().parents[2]
|
||||
templates = Jinja2Templates(directory=str(project_root / "data" / "templates"))
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
@router.get("/", response_model=None, include_in_schema=False)
|
||||
async def dashboard(
|
||||
request: Request,
|
||||
recon_job_name: str | None = Query(default=None),
|
||||
as_at_date: str | None = Query(default=None),
|
||||
) -> HTMLResponse:
|
||||
) -> HTMLResponse | RedirectResponse:
|
||||
user = request.session.get("user")
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
# if not user:
|
||||
# return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
transactions = list_transactions()
|
||||
|
||||
if as_at_date:
|
||||
transactions = [item for item in transactions if item.date.isoformat() == as_at_date]
|
||||
transactions = [
|
||||
item for item in transactions if item.date.isoformat() == as_at_date
|
||||
]
|
||||
|
||||
results = [
|
||||
{
|
||||
"Transaction ID": item.transaction_id,
|
||||
"Txn ID": item.transaction_id,
|
||||
"Date": item.date.isoformat(),
|
||||
"Ref ID": item.reference_id,
|
||||
"Account": item.account,
|
||||
"Counterparty": item.counterparty,
|
||||
"Amount": f"{item.amount:,.2f}",
|
||||
"CCY": item.currency,
|
||||
"Booking": item.booking_date.isoformat(),
|
||||
"Settlement": item.settlement_date.isoformat(),
|
||||
"Description": item.description,
|
||||
"Status": item.status,
|
||||
"Flag": item.flag,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user