Currency Documentation¶
Overview¶
The Currency module (currency) manages all currencies used in the system, including exchange rate history, currency conversions, and multi-currency transaction support. This is the foundation for international business operations, allowing the system to handle transactions in multiple currencies with automatic conversion based on exchange rates.
Model Information¶
Model Name: currency
Display Name: Currency
Name Field: code
Key Fields: name (currency name must be unique)
Features¶
- ✅ Audit logging enabled
- ✅ Historical exchange rate tracking
- ✅ Buy and sell rate support
- ✅ Multi-company rate management
- ✅ Currency conversion engine
- ✅ Rate caching for performance
- ✅ IPC cache synchronization across processes
Understanding Currency Management¶
What is the Currency Model?¶
The Currency model stores:
1. Currency Definitions - Name, code, symbol, ISO details
2. Exchange Rates - Historical buy/sell rates via currency.rate
3. Conversion Logic - Methods to convert between currencies
4. Accounting Setup - Accounts for currency trading gains/losses
Key Concepts¶
Buy vs. Sell Rates: - Buy Rate: Rate at which you buy foreign currency (used for payments, purchases) - Sell Rate: Rate at which you sell foreign currency (used for receipts, sales) - Example: Bank buys USD at 3.70, sells at 3.75 (spread covers their profit)
Rate History: - Each currency has multiple rate records over time - System uses the latest applicable rate for a given date - Allows accurate historical reporting and conversions
Rate Caching: - Frequently used rates are cached in memory - Cache cleared when rates are updated via IPC signals - Improves performance for high-volume transactions
Understanding Key Fields¶
What are Key Fields?¶
For the currency model, the key field is:
This means the currency name must be unique across all currency records.
Why This Matters¶
Uniqueness Guarantee:
# Valid - unique names
"United States Dollar" ✅ Valid
"Euro" ✅ Valid
"Thai Baht" ✅ Valid
# This would fail - duplicate name:
"United States Dollar" ❌ ERROR: Currency name already exists!
Key Fields Reference¶
Core Currency Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Char | ✅ | Full currency name (e.g., "United States Dollar") |
code |
Char | ✅ | ISO currency code (e.g., "USD", "EUR", "THB") |
sign |
Char | ✅ | Currency symbol (e.g., "$", "€", "฿") |
iso_number |
Char | ❌ | ISO 4217 numeric code (e.g., "840" for USD) |
cents_name |
Char | ❌ | Name of fractional unit (e.g., "cents", "pence") |
Exchange Rate Fields¶
| Field | Type | Description |
|---|---|---|
rates |
One2Many | Historical exchange rates (currency.rate) |
sell_rate |
Decimal | Current selling rate (computed from latest rate) |
buy_rate |
Decimal | Current buying rate (computed from latest rate) |
Accounting Fields¶
| Field | Type | Description |
|---|---|---|
product_id |
Many2One | Product representing this currency for trading |
account_receivable_id |
Many2One | A/R account for currency exchange gains |
account_payable_id |
Many2One | A/P account for currency exchange losses |
Communication Fields¶
| Field | Type | Description |
|---|---|---|
comments |
One2Many | Comments and notes about this currency |
API Methods¶
1. Create Currency¶
Method: create(vals, context)
Creates a new currency and clears the rate cache across all processes.
Parameters:
vals = {
"name": "United States Dollar", # Required
"code": "USD", # Required
"sign": "$", # Required
"iso_number": "840", # Optional
"cents_name": "cents" # Optional
}
Returns: int - New currency ID
Example:
# Create a new currency
currency_id = get_model("currency").create({
"name": "United States Dollar",
"code": "USD",
"sign": "$",
"iso_number": "840",
"cents_name": "cents"
})
Behavior: - Clears currency cache via IPC signal - Validates unique currency name - Triggers audit log entry
2. Get Current Rate¶
Method: get_current_rate(ids, context={})
Retrieves the most recent buy and sell rates for currencies.
Parameters:
- ids (list): Currency IDs
- context (dict): Optional context
Returns: dict - Current rates for each currency
Example:
currency = get_model("currency").browse([usd_id])[0]
print(f"Current Sell Rate: {currency.sell_rate}")
print(f"Current Buy Rate: {currency.buy_rate}")
Behavior: - Returns rates from the first (most recent) rate record - Returns None if no rates exist for currency
3. Get Rate for Date¶
Method: get_rate(ids, date=None, rate_type="buy", context={})
Retrieves the exchange rate for a specific date and rate type.
Parameters:
- ids (list): Currency ID list (uses first ID)
- date (str): Date for rate lookup (format: "YYYY-MM-DD")
- rate_type (str): "buy" or "sell"
- context (dict): Options including no_cache to bypass cache
Returns: Decimal - Exchange rate or None if not found
Example:
# Get buy rate for specific date
rate = get_model("currency").get_rate(
[usd_id],
date="2025-01-15",
rate_type="buy"
)
print(f"USD buy rate on 2025-01-15: {rate}")
# Get latest rate (no date specified)
current_rate = get_model("currency").get_rate([usd_id], rate_type="sell")
Behavior:
1. Checks cache first (unless no_cache=True in context)
2. Looks for company-specific rate first
3. Falls back to general rate if company-specific not found
4. Uses latest rate on or before specified date
5. Caches result for future lookups
Context Options:
4. Convert Currency¶
Method: convert(amt, cur_from_id, cur_to_id, from_rate=None, to_rate=None, rate=None, round=False, date=None, rate_type="buy", context={})
Converts an amount from one currency to another.
Parameters:
- amt (Decimal): Amount to convert
- cur_from_id (int): Source currency ID
- cur_to_id (int): Target currency ID
- from_rate (Decimal): Optional - source currency rate (if not provided, fetched automatically)
- to_rate (Decimal): Optional - target currency rate (if not provided, fetched automatically)
- rate (Decimal): Optional - direct conversion rate
- round (bool): Whether to round result to 2 decimals
- date (str): Date for rate lookup
- rate_type (str): "buy" or "sell"
- context (dict): Additional options
Returns: Decimal - Converted amount
Example:
# Convert 1000 USD to THB
thb_amount = get_model("currency").convert(
amt=1000,
cur_from_id=usd_id,
cur_to_id=thb_id,
date="2025-01-15",
rate_type="buy",
round=True
)
print(f"1000 USD = {thb_amount} THB")
# Convert using specific rates
thb_amount = get_model("currency").convert(
amt=1000,
cur_from_id=usd_id,
cur_to_id=thb_id,
from_rate=1.0,
to_rate=35.5,
round=True
)
Conversion Formula:
# Standard conversion
converted_amount = amount * from_rate / to_rate
# Direct rate conversion (if rate parameter provided)
converted_amount = amount * rate
Behavior:
- Returns original amount if currencies are the same
- Fetches rates automatically if not provided
- Prints warning if rate is missing (sets rate to 0)
- Rounds to 2 decimals if round=True
5. Round Amount¶
Method: round(cur_id, amt)
Rounds an amount to 2 decimal places using ROUND_HALF_UP method.
Parameters:
- cur_id (int): Currency ID (not actually used in current implementation)
- amt (Decimal): Amount to round
Returns: Decimal - Rounded amount
Example:
Rounding Method: ROUND_HALF_UP - 0.5 and above rounds up - Below 0.5 rounds down
Search Functions¶
Find Currency by Code¶
# Find USD currency
currencies = get_model("currency").search_browse([
["code", "=", "USD"]
])
if currencies:
usd = currencies[0]
print(f"{usd.name} ({usd.code}) - Symbol: {usd.sign}")
Find All Active Currencies¶
# Get all currencies with rates
currencies = get_model("currency").search_browse([
["rates", "!=", None]
])
for curr in currencies:
print(f"{curr.code}: Buy {curr.buy_rate}, Sell {curr.sell_rate}")
Search by Name¶
# Search currency by name
currencies = get_model("currency").search_browse([
["name", "ilike", "dollar"]
])
Computed Fields Functions¶
get_current_rate(ids, context)¶
Retrieves the most recent exchange rate (first rate in the rates relationship).
Logic: - Sorts rates by date (latest first) - Returns sell_rate and buy_rate from first rate record - Returns None if no rates exist
Best Practices¶
1. Always Specify Rate Type¶
# Bad: Using default rate type might not be appropriate
amount = get_model("currency").convert(1000, usd_id, thb_id)
# Good: Explicitly specify rate type based on transaction
# For purchases/payments (you're buying foreign currency)
amount = get_model("currency").convert(
1000, usd_id, thb_id,
rate_type="buy"
)
# For sales/receipts (you're selling foreign currency)
amount = get_model("currency").convert(
1000, thb_id, usd_id,
rate_type="sell"
)
2. Use Date-Specific Rates for Historical Data¶
# Bad: Using current rate for historical transactions
amount = get_model("currency").convert(1000, usd_id, thb_id)
# Good: Use transaction date for accurate conversion
transaction_date = "2024-06-15"
amount = get_model("currency").convert(
1000, usd_id, thb_id,
date=transaction_date,
rate_type="buy"
)
3. Round Monetary Amounts¶
# Bad: Keeping full precision (can cause rounding issues)
amount = get_model("currency").convert(1000.50, usd_id, thb_id)
# Result might be: 35,517.75000000001
# Good: Round to 2 decimals for money
amount = get_model("currency").convert(
1000.50, usd_id, thb_id,
round=True
)
# Result: 35,517.75
4. Handle Missing Rates Gracefully¶
# Check if rate exists before converting
currency_id = thb_id
rate = get_model("currency").get_rate([currency_id], date="2025-01-15")
if rate is None:
print(f"⚠ Warning: No exchange rate found for date")
# Handle error appropriately
else:
amount = get_model("currency").convert(
1000, usd_id, currency_id,
to_rate=rate,
round=True
)
Database Constraints¶
Unique Currency Name¶
Implications: - Cannot create duplicate currency names - Currency code can be the same as name in unique constraint - Use descriptive, standardized names
Related Models¶
| Model | Relationship | Description |
|---|---|---|
currency.rate |
One2Many | Historical exchange rate records |
account.account |
Many2One | Receivable/Payable accounts for trading |
product |
Many2One | Product representing currency for trading |
account.invoice |
Referenced | Invoices with foreign currency |
account.payment |
Referenced | Payments in foreign currency |
account.move.line |
Referenced | Journal lines with currency amounts |
Common Use Cases¶
Use Case 1: Set Up a New Currency¶
# 1. Create the currency
eur_id = get_model("currency").create({
"name": "Euro",
"code": "EUR",
"sign": "€",
"iso_number": "978",
"cents_name": "cents"
})
# 2. Add initial exchange rate
get_model("currency.rate").create({
"currency_id": eur_id,
"date": "2025-01-15",
"buy_rate": 0.93, # 1 EUR = 0.93 base currency
"sell_rate": 0.95
})
# 3. Verify
eur = get_model("currency").browse([eur_id])[0]
print(f"Currency: {eur.name} ({eur.code})")
print(f"Buy Rate: {eur.buy_rate}")
print(f"Sell Rate: {eur.sell_rate}")
Use Case 2: Convert Invoice Amount¶
# Scenario: Customer invoice in USD, need amount in THB
invoice_amount_usd = 5000
invoice_date = "2025-01-20"
# Get currency IDs
usd_id = get_model("currency").search([["code", "=", "USD"]])[0]
thb_id = get_model("currency").search([["code", "=", "THB"]])[0]
# Convert to THB (selling USD, use sell rate)
amount_thb = get_model("currency").convert(
amt=invoice_amount_usd,
cur_from_id=usd_id,
cur_to_id=thb_id,
date=invoice_date,
rate_type="sell", # Customer paying us, we sell USD
round=True
)
print(f"Invoice Amount: ${invoice_amount_usd:,.2f} USD")
print(f"Equivalent: ฿{amount_thb:,.2f} THB")
Use Case 3: Update Daily Exchange Rates¶
# Daily job to update exchange rates from external source
from datetime import date
today = str(date.today())
# Rates fetched from external API
exchange_rates = {
"USD": {"buy": 1.0, "sell": 1.0}, # Base currency
"EUR": {"buy": 0.93, "sell": 0.95},
"GBP": {"buy": 0.82, "sell": 0.84},
"THB": {"buy": 35.5, "sell": 36.0}
}
# Update rates for each currency
for code, rates in exchange_rates.items():
# Find currency
currency_ids = get_model("currency").search([["code", "=", code]])
if not currency_ids:
print(f"⚠ Currency {code} not found")
continue
currency_id = currency_ids[0]
# Check if rate already exists for today
existing = get_model("currency.rate").search([
["currency_id", "=", currency_id],
["date", "=", today]
])
if existing:
# Update existing rate
get_model("currency.rate").write(existing, {
"buy_rate": rates["buy"],
"sell_rate": rates["sell"]
})
print(f"✓ Updated rate for {code}")
else:
# Create new rate
get_model("currency.rate").create({
"currency_id": currency_id,
"date": today,
"buy_rate": rates["buy"],
"sell_rate": rates["sell"]
})
print(f"✓ Created rate for {code}")
print("Exchange rates updated successfully")
Use Case 4: Calculate Exchange Gain/Loss¶
# Scenario: Paid invoice in foreign currency at different rate
invoice_amount_usd = 1000
invoice_rate = 35.0 # Rate when invoice created
payment_rate = 36.0 # Rate when payment received
# Get currency IDs
usd_id = get_model("currency").search([["code", "=", "USD"]])[0]
thb_id = get_model("currency").search([["code", "=", "THB"]])[0]
# Amount at invoice rate
amount_at_invoice = get_model("currency").convert(
invoice_amount_usd, usd_id, thb_id,
rate=invoice_rate, round=True
)
# Amount at payment rate
amount_at_payment = get_model("currency").convert(
invoice_amount_usd, usd_id, thb_id,
rate=payment_rate, round=True
)
# Calculate gain/loss
exchange_diff = amount_at_payment - amount_at_invoice
if exchange_diff > 0:
print(f"Exchange Gain: ฿{exchange_diff:,.2f}")
else:
print(f"Exchange Loss: ฿{abs(exchange_diff):,.2f}")
Use Case 5: Multi-Currency Trial Balance¶
# Generate trial balance with all amounts in base currency
base_currency_id = get_model("currency").search([["code", "=", "THB"]])[0]
# Get all accounts with balances
accounts = get_model("account.account").search_browse([])
print("TRIAL BALANCE (All amounts in THB)")
print("=" * 80)
for account in accounts:
# Get balance in account's currency
balance = account.balance # Assume in foreign currency
account_currency_id = account.currency_id.id if account.currency_id else base_currency_id
# Convert to base currency
if account_currency_id != base_currency_id:
balance_thb = get_model("currency").convert(
balance,
account_currency_id,
base_currency_id,
round=True
)
else:
balance_thb = balance
print(f"{account.code:10} {account.name:40} {balance_thb:15,.2f}")
Performance Tips¶
1. Rate Caching¶
The system automatically caches exchange rates. Avoid bypassing cache unless necessary:
# Good: Uses cache
rate = get_model("currency").get_rate([usd_id])
# Slower: Bypasses cache
rate = get_model("currency").get_rate([usd_id], context={"no_cache": True})
2. Batch Conversions¶
When converting multiple amounts, reuse fetched rates:
# Bad: Fetches rate for each conversion
for amount in amounts:
converted = get_model("currency").convert(amount, usd_id, thb_id)
# Good: Fetch rate once, reuse
rate = get_model("currency").get_rate([usd_id], rate_type="buy")
for amount in amounts:
converted = get_model("currency").convert(
amount, usd_id, thb_id,
from_rate=rate, to_rate=1
)
3. Index on Currency Code¶
Troubleshooting¶
"WARNING: missing rate for currency X"¶
Cause: No exchange rate defined for the currency on specified date Solution: Add exchange rate for the currency:
get_model("currency.rate").create({
"currency_id": currency_id,
"date": "2025-01-15",
"buy_rate": 1.0,
"sell_rate": 1.0
})
"Currency name already exists"¶
Cause: Trying to create currency with duplicate name Solution: Use unique names or update existing currency:
# Check if exists first
existing = get_model("currency").search([["name", "=", "US Dollar"]])
if existing:
currency_id = existing[0]
else:
currency_id = get_model("currency").create({...})
"Conversion returns 0"¶
Cause: Missing to_rate results in division by zero protection Solution: Ensure rates exist for both currencies:
# Verify rates exist
from_rate = get_model("currency").get_rate([from_currency_id])
to_rate = get_model("currency").get_rate([to_currency_id])
if not from_rate or not to_rate:
print("Error: Missing exchange rates")
"Incorrect conversion on historical date"¶
Cause: Using current rate instead of historical rate Solution: Always pass the transaction date:
# Wrong
amount = get_model("currency").convert(1000, usd_id, thb_id)
# Correct
amount = get_model("currency").convert(
1000, usd_id, thb_id,
date=transaction_date
)
Testing Examples¶
Unit Test: Currency Conversion¶
def test_currency_conversion():
# Create test currencies
usd_id = get_model("currency").create({
"name": "Test USD",
"code": "TUSD",
"sign": "$"
})
thb_id = get_model("currency").create({
"name": "Test THB",
"code": "TTHB",
"sign": "฿"
})
# Create rates
get_model("currency.rate").create({
"currency_id": usd_id,
"date": "2025-01-15",
"buy_rate": 1.0,
"sell_rate": 1.0
})
get_model("currency.rate").create({
"currency_id": thb_id,
"date": "2025-01-15",
"buy_rate": 35.0,
"sell_rate": 36.0
})
# Test conversion
amount_thb = get_model("currency").convert(
100, usd_id, thb_id,
date="2025-01-15",
rate_type="buy",
round=True
)
assert amount_thb == 3500.00, f"Expected 3500.00, got {amount_thb}"
print("✓ Currency conversion test passed")
# Cleanup
get_model("currency").delete([usd_id, thb_id])
Security Considerations¶
Permission Model¶
- Currency creation/modification requires administrative access
- Exchange rate updates may require specific accounting permissions
- Audit log tracks all currency changes
Data Access¶
- Currencies are global (not company-specific)
- Rates can be company-specific or global
- Multi-company installations should verify rate access controls
Integration Points¶
Internal Modules¶
- currency.rate: Exchange rate history
- account.invoice: Multi-currency invoicing
- account.payment: Multi-currency payments
- account.move.line: Journal lines with currency amounts
- product: Currency as tradeable product
External Systems¶
- Exchange rate APIs (for automated updates)
- Banking systems (for rate imports)
- Financial reporting tools
Version History¶
Last Updated: 2025-12-16 Model Version: currency.py Framework: Netforce
Additional Resources¶
- Currency Rate Documentation:
currency.rate - Invoice Documentation:
account.invoice - Payment Documentation:
account.payment - Multi-Currency Accounting Guide
Support & Feedback¶
For issues or questions about currencies: 1. Verify exchange rates are current and complete 2. Check rate type (buy vs. sell) for transactions 3. Review currency cache if rates seem stale 4. Test conversions with known values
This documentation is generated for developer onboarding and reference purposes.