Skip to main content
OptiFlow supports multi-currency operations, allowing you to manage transactions in different currencies with automatic conversion based on exchange rates. This is essential for businesses dealing with international suppliers or customers.

Currency System Overview

The currency system consists of:
  • Currencies: Supported currency definitions (code, symbol, name)
  • Exchange Rates: Historical exchange rates with effective dates
  • Default Currency: The base currency for your business (typically DOP for Dominican Republic)

Currency Model

The Currency model (app/Models/Currency.php:41) contains:
$currency->code;        // ISO 4217 code (e.g., "USD", "EUR", "DOP")
$currency->name;        // Full name (e.g., "US Dollar")
$currency->symbol;      // Currency symbol (e.g., "$", "€", "RD$")
$currency->is_default;  // Whether this is the default currency
$currency->is_active;   // Whether this currency is active

Common Currencies

CodeNameSymbol
DOPDominican PesoRD$
USDUS Dollar$
EUREuro

Setting Up Currencies

Creating a Currency

Use the CurrencyController (app/Http/Controllers/CurrencyController.php:18) or the action:
1

Navigate to Currencies

Access the currencies management page:
GET /currencies
2

Create New Currency

Click “Create” and provide:
  • Currency code (3-letter ISO code)
  • Full name
  • Symbol
  • Whether it’s active
3

Set Exchange Rate

After creating the currency, add an initial exchange rate

Using CreateCurrencyAction

use App\Actions\CreateCurrencyAction;

$action = new CreateCurrencyAction();
$action->handle([
    'code' => 'USD',
    'name' => 'US Dollar',
    'symbol' => '$',
    'is_active' => true,
    'is_default' => false,
]);

Setting the Default Currency

Only one currency can be the default:
// When creating/updating, setting is_default = true
// automatically unsets other currencies as default
$usd = Currency::where('code', 'USD')->first();
$usd->is_default = true;
$usd->save();
// All other currencies will have is_default = false
The default currency always has an exchange rate of 1.0. All other currencies are converted relative to the default currency.

Exchange Rates

Exchange Rate Model

The CurrencyRate model stores historical exchange rates:
$rate->currency_id;     // Foreign key to currency
$rate->rate;            // Exchange rate (e.g., 56.5 DOP per USD)
$rate->effective_date;  // When this rate becomes effective

Creating Exchange Rates

use App\Actions\CreateCurrencyRateAction;
use Carbon\Carbon;

$action = new CreateCurrencyRateAction();
$action->handle($currency, [
    'rate' => 56.50,
    'effective_date' => Carbon::now()->format('Y-m-d'),
]);

How Rates Work

Exchange rates represent how much of the default currency equals 1 unit of the foreign currency:
If default currency is DOP and foreign currency is USD:
Rate = 56.5 means 1 USD = 56.5 DOP

To convert USD to DOP: USD × rate
To convert DOP to USD: DOP ÷ rate

Historical Rates

Rates are date-effective, allowing you to:
  • Track rate changes over time
  • Use historical rates for past transactions
  • Calculate currency variations
// Get rate for specific date
$rate = $currency->getRateForDate(Carbon::parse('2024-01-15'));

// Get current rate
$currentRate = $currency->getCurrentRate();
Always use the transaction date when converting currencies for invoices to ensure accurate historical records.

Currency Selection in Transactions

When creating invoices or other transactions:

Selecting Currency

use App\Models\Currency;
use App\Models\Invoice;

// Get active currencies for selection
$currencies = Currency::active()->get();

// Create invoice with specific currency
$invoice = new Invoice();
$invoice->currency_id = $usd->id;
$invoice->amount = 1000; // Amount in USD
$invoice->save();

Storing Both Amounts

Best practice is to store both original and converted amounts:
$invoice->currency_id = $usd->id;
$invoice->amount = 1000; // Original amount in USD
$invoice->amount_in_default_currency = $usd->convertToDefault(1000);
// 1000 × 56.5 = 56,500 DOP

Currency Conversion

The Currency model provides conversion methods:

Convert from Default Currency

$usd = Currency::where('code', 'USD')->first();

// Convert 56,500 DOP to USD
$amountInUsd = $usd->convertFromDefault(56500);
// 56500 ÷ 56.5 = 1000 USD

Convert to Default Currency

// Convert 1000 USD to DOP
$amountInDop = $usd->convertToDefault(1000);
// 1000 × 56.5 = 56,500 DOP

Convert with Historical Rates

use Carbon\Carbon;

$historicalDate = Carbon::parse('2024-01-15');

// Convert using rate from specific date
$amountInDop = $usd->convertToDefault(1000, $historicalDate);

Format Currency Amounts

$formatted = $usd->formatAmount(1000.50);
// Returns: "$ 1,000.50"

$formattedDop = $dop->formatAmount(56500);
// Returns: "RD$ 56,500.00"

Exchange Rate Variations

Calculating Rate Changes

// Get percentage variation from previous rate
$variation = $currency->getVariation();
// Returns: 2.5 (meaning 2.5% increase from last rate)
This compares the latest two exchange rates:
// If previous rate was 56.0 and current rate is 56.5:
$variation = ((56.5 - 56.0) / 56.0) × 100 = 0.89%

Displaying Rate History

use App\Models\CurrencyRate;

// Get historical rates for charting
$rates = CurrencyRate::query()
    ->where('currency_id', $usd->id)
    ->where('effective_date', '>=', now()->subMonths(6))
    ->orderBy('effective_date', 'asc')
    ->get();

// Format for frontend chart
$chartData = $rates->map(fn($rate) => [
    'date' => $rate->effective_date->format('Y-m-d'),
    'rate' => (float) $rate->rate,
]);

Managing Currencies

Listing Currencies

The currency index shows all currencies with current rates:
$currencies = Currency::with(['rates' => function ($query) {
    $query->latest('effective_date')->limit(1);
}])->get();

foreach ($currencies as $currency) {
    $currentRate = $currency->getCurrentRate();
    $variation = $currency->getVariation();
    
    // Display currency info
}

Updating Currencies

use App\Actions\UpdateCurrencyAction;

$action = new UpdateCurrencyAction();
$action->handle($currency, [
    'name' => 'Updated Name',
    'symbol' => '$',
    'is_active' => true,
]);

Deleting Currencies

use App\Actions\DeleteCurrencyAction;

$action = new DeleteCurrencyAction();
try {
    $action->handle($currency);
} catch (ActionValidationException $e) {
    // Cannot delete: currency is in use by transactions
}
You cannot delete a currency that is referenced by existing invoices or transactions. Set it as inactive instead.

Currency Scopes

The model provides useful query scopes:
// Get only active currencies
$activeCurrencies = Currency::active()->get();

// Get default currency
$defaultCurrency = Currency::default()->first();

// Combine scopes
$activeNonDefault = Currency::active()
    ->where('is_default', false)
    ->get();

Best Practices

  1. Update Rates Regularly: Set up a scheduled task to update exchange rates daily
  2. Lock Historical Data: Never modify past exchange rates once transactions exist
  3. Default Currency: Choose your accounting currency as the default (usually local currency)
  4. Rate Sources: Use reliable sources for exchange rates (central bank, financial APIs)
  5. Precision: Store rates with sufficient decimal places (e.g., 4-6 decimals)
  6. Display Formats: Use proper currency formatting for each currency in the UI
  7. Audit Trail: Log all rate changes for compliance

Automated Rate Updates

Implement a scheduled task to fetch rates from an API:
namespace App\Console\Commands;

use App\Models\Currency;
use App\Actions\CreateCurrencyRateAction;
use Illuminate\Support\Facades\Http;

class UpdateExchangeRates extends Command
{
    protected $signature = 'rates:update';
    
    public function handle(CreateCurrencyRateAction $action)
    {
        $defaultCurrency = Currency::default()->first();
        $currencies = Currency::active()
            ->where('is_default', false)
            ->get();
        
        foreach ($currencies as $currency) {
            // Fetch rate from API (example)
            $response = Http::get('https://api.exchangerate.host/latest', [
                'base' => $defaultCurrency->code,
                'symbols' => $currency->code,
            ]);
            
            if ($response->successful()) {
                $rate = $response->json()['rates'][$currency->code];
                
                $action->handle($currency, [
                    'rate' => $rate,
                    'effective_date' => now()->format('Y-m-d'),
                ]);
            }
        }
        
        $this->info('Exchange rates updated successfully.');
    }
}
Schedule it in routes/console.php:
Schedule::command('rates:update')->daily();

Multi-Currency Reports

When generating reports with multiple currencies:
// Get invoices grouped by currency
$invoicesByCurrency = Invoice::query()
    ->selectRaw('currency_id, SUM(amount) as total')
    ->groupBy('currency_id')
    ->with('currency')
    ->get();

// Display totals
foreach ($invoicesByCurrency as $group) {
    $formatted = $group->currency->formatAmount($group->total);
    echo "{$group->currency->code}: {$formatted}\n";
}

// Convert all to default currency for grand total
$grandTotal = $invoicesByCurrency->sum(function ($group) {
    return $group->currency->convertToDefault($group->total);
});

Build docs developers (and LLMs) love