Skip to content

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:

_key = ["name"]

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

{
    currency_id: {
        "sell_rate": 3.75,
        "buy_rate": 3.70
    }
}

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:

context = {
    "no_cache": True  # Force database lookup, skip cache
}


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:

rounded = get_model("currency").round(usd_id, 123.456)
print(rounded)  # Output: 123.46

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

_sql_constraints = [
    ("name_unique",
     "unique (name)",
     "Currency name must be unique")
]

Implications: - Cannot create duplicate currency names - Currency code can be the same as name in unique constraint - Use descriptive, standardized names


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

CREATE INDEX idx_currency_code ON currency(code);
CREATE INDEX idx_currency_name ON currency(name);

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.