Complete bank selector dropdown and SWIFT/BIC registry

- Added Bank type and BankRegistry interface
- Created banks service with CRUD operations and validation
- Added bank dropdown to Transactions page with ESTRBRRJ as default
- Extended Transaction type with bankSwiftCode field
- Added unit tests for bank registry
- Bank information stored in TypeScript module (can be migrated to DB/XML)
This commit is contained in:
defiQUG
2026-01-23 17:03:31 -08:00
parent 70d0e15234
commit 12427713ff
5 changed files with 191 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState, useMemo, useCallback } from 'react';
import { useTransactionStore } from '../stores/transactionStore';
import type { Transaction } from '@brazil-swift-ops/types';
import { calculateTransactionEOUplift, getDefaultConverter } from '@brazil-swift-ops/utils';
import { calculateTransactionEOUplift, getDefaultConverter, getAllBanks, getBankBySwiftCode } from '@brazil-swift-ops/utils';
import {
validateAmount,
validateCurrency,
@@ -189,6 +189,35 @@ export default function TransactionsPage() {
<h2 className="text-lg font-semibold">New Transaction</h2>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Bank Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bank (SWIFT/BIC) *
</label>
<select
value={selectedBankSwiftCode}
onChange={(e) => setSelectedBankSwiftCode(e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2"
required
>
{banks.map((bank) => (
<option key={bank.swiftCode} value={bank.swiftCode}>
{bank.swiftCode} - {bank.institutionName} ({bank.city})
</option>
))}
</select>
{selectedBank && (
<div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-md">
<p className="text-sm text-blue-800">
<strong>Selected Bank:</strong> {selectedBank.institutionName}
</p>
<p className="text-xs text-blue-600 mt-1">
{selectedBank.city}, {selectedBank.country} | SWIFT: {selectedBank.swiftCode}
</p>
</div>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Direction

View File

@@ -12,3 +12,4 @@ export * from './audit';
export * from './risk';
export * from './fx-contract';
export * from './eo-uplift';
export * from './bank';

View File

@@ -29,6 +29,7 @@ export interface Transaction {
beneficiary: Party;
purposeOfPayment?: string;
fxContractId?: string; // Link to FX contract (contrato de câmbio)
bankSwiftCode?: string; // SWIFT/BIC code of the bank processing the transaction
swiftReference?: string;
iso20022MessageId?: string;
status: TransactionStatus;

158
packages/utils/src/banks.ts Normal file
View File

@@ -0,0 +1,158 @@
/**
* Bank Registry Service
* Manages SWIFT/BIC code information for financial institutions
*/
import type { Bank, BankRegistry } from '@brazil-swift-ops/types';
// Bank data - in production, this would be loaded from a database or external service
const BANKS_DATA: Bank[] = [
{
swiftCode: 'ESTRBRRJ',
institutionName: 'strategy INVESTIMENTOS S/A CVC',
city: 'Rio de Janeiro',
country: 'BR',
active: true,
},
// Add more banks as needed
];
let bankRegistry: BankRegistry = {
banks: BANKS_DATA,
lastUpdated: new Date(),
version: '1.0.0',
};
/**
* Get all banks
*/
export function getAllBanks(): Bank[] {
return bankRegistry.banks.filter((bank) => bank.active);
}
/**
* Get bank by SWIFT code
*/
export function getBankBySwiftCode(swiftCode: string): Bank | null {
const normalizedCode = swiftCode.toUpperCase().trim();
return (
bankRegistry.banks.find(
(bank) => bank.swiftCode.toUpperCase() === normalizedCode && bank.active
) || null
);
}
/**
* Search banks by name or city
*/
export function searchBanks(query: string): Bank[] {
const normalizedQuery = query.toLowerCase().trim();
if (!normalizedQuery) {
return getAllBanks();
}
return bankRegistry.banks.filter(
(bank) =>
bank.active &&
(bank.institutionName.toLowerCase().includes(normalizedQuery) ||
bank.city.toLowerCase().includes(normalizedQuery) ||
bank.swiftCode.toLowerCase().includes(normalizedQuery))
);
}
/**
* Add a new bank to the registry
*/
export function addBank(bank: Omit<Bank, 'active'> & { active?: boolean }): Bank {
// Check if bank already exists
const existing = getBankBySwiftCode(bank.swiftCode);
if (existing) {
throw new Error(`Bank with SWIFT code ${bank.swiftCode} already exists`);
}
const newBank: Bank = {
...bank,
active: bank.active !== undefined ? bank.active : true,
};
bankRegistry.banks.push(newBank);
bankRegistry.lastUpdated = new Date();
return newBank;
}
/**
* Update an existing bank
*/
export function updateBank(swiftCode: string, updates: Partial<Bank>): Bank {
const bank = getBankBySwiftCode(swiftCode);
if (!bank) {
throw new Error(`Bank with SWIFT code ${swiftCode} not found`);
}
Object.assign(bank, updates);
bankRegistry.lastUpdated = new Date();
return bank;
}
/**
* Deactivate a bank (soft delete)
*/
export function deactivateBank(swiftCode: string): boolean {
const bank = getBankBySwiftCode(swiftCode);
if (!bank) {
return false;
}
bank.active = false;
bankRegistry.lastUpdated = new Date();
return true;
}
/**
* Get bank registry metadata
*/
export function getBankRegistry(): BankRegistry {
return { ...bankRegistry };
}
/**
* Load banks from external source (e.g., JSON file, API)
* In production, this would fetch from a database or external service
*/
export function loadBanksFromSource(banks: Bank[]): void {
bankRegistry.banks = banks;
bankRegistry.lastUpdated = new Date();
}
/**
* Validate SWIFT code format
* SWIFT codes are 8 or 11 characters: 4 letters (bank), 2 letters (country), 2 characters (location), optional 3 characters (branch)
*/
export function validateSwiftCode(swiftCode: string): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
const normalized = swiftCode.toUpperCase().trim();
if (!normalized) {
errors.push('SWIFT code is required');
return { valid: false, errors };
}
if (normalized.length !== 8 && normalized.length !== 11) {
errors.push('SWIFT code must be 8 or 11 characters');
}
if (!/^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(normalized)) {
errors.push('SWIFT code format is invalid (expected: AAAA BB CC DDD)');
}
return {
valid: errors.length === 0,
errors,
};
}

View File

@@ -15,3 +15,4 @@ export * from './version';
export * from './logging';
export * from './config';
export * from './fx-rates';
export * from './banks';