- Fix exchange-rate service types to match actual backend response shapes
(exchangeRate array, activeProvider success/error, used currencies as strings)
- Add ExchangeRateConverter to payments, expenses, and recurring invoices
- Set currency_id from customer currency in invoice/estimate selectCustomer()
- Load globalStore.currencies in ExchangeRateConverter on mount
- Pass driver/key/driver_config params to getSupportedCurrencies in provider modal
- Fix OpenExchangeRateDriver validateConnection to use base=USD (free plan compat)
- Fix checkActiveCurrencies SQLite whereJsonContains with array values
- Remove broken currency/companyCurrency props from ExpenseCreateView, use stores
- Show base currency equivalent in document line items and totals when exchange
rate is active
Replace duplicated switch/case blocks across 4 methods with a clean
abstract driver pattern:
- ExchangeRateDriver (abstract): defines getExchangeRate(),
getSupportedCurrencies(), validateConnection()
- CurrencyFreakDriver, CurrencyLayerDriver, OpenExchangeRateDriver,
CurrencyConverterDriver: concrete implementations
- ExchangeRateDriverFactory: resolves driver name to class, with
register() method for module extensibility
Delete ExchangeRateProvidersTrait — all logic now lives in driver
classes and ExchangeRateProviderService. Adding a new exchange rate
provider only requires implementing ExchangeRateDriver and calling
ExchangeRateDriverFactory::register() in a module service provider.