---
id: CAP-ACC-BKS-004
title: Maintain dated exchange rates for a unit
feature: FEAT-ACC-BKS-01
product_area: accounting
module: books
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - CAP-ACC-CFG-015
  - CAP-ACC-SEC-012
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-accounting:docs/milestones/plans/ACCT-M1-plan.md#2. Scope'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-3'
  - 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-4'
  - 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-5'
purpose: >-
  Finance staff maintain precise foreign-currency quotes and resolve the rate
  applicable to a date.
outcomes:
  - Dated resolution selects the latest eligible quote.
  - >-
    Provider quote correction uses removal and refilling rather than manual
    editing.
  - Commerce and Accounting share the same resolved quote.
concepts:
  - CON-ACC-BKS-001
actors:
  - ACT-ACC-001
  - ACT-ACC-002
  - ACT-ACC-004
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: accounting.exchange-rates.read
    where_checked: api
    scope: tenant
  - key: accounting.exchange-rates.create
    where_checked: api
    scope: tenant
  - key: accounting.exchange-rates.update
    where_checked: api
    scope: tenant
  - key: accounting.exchange-rates.delete
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-ACC-BKS-004-01
    proved_by: [ATS-ACC-BKS-004-01, ATS-ACC-BKS-004-09, TST-ACC-BKS-053, TST-ACC-BKS-172]
    statement: >-
      A rate is positive decimal text with no more than eight integer and ten
      fractional digits for a supported non-base currency on a valid date;
      numeric input is refused to preserve precision.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/unit/rules/exchange-rate-rules.spec.js#refuses
      a number (digits would be lost), zero, a negative, more than ten places,
      nine integer digits and a word
    origin: 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-5'
    examples:
      positive: A valid configuration completes the stated operation.
      negative: A conflicting configuration receives the stated refusal.
  - id: BR-CAP-ACC-BKS-004-02
    proved_by: [ATS-ACC-BKS-002-12, ATS-ACC-BKS-004-02, TST-ACC-BKS-084]
    statement: >-
      A unit has at most one rate for each currency and date, and a saved rate
      cannot change its unit, currency, date or source.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.integration.spec.js#refuses
      changing the unit, the currency or the day: a rate’s identity is
      write-once
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-BKS-004-03
    proved_by: [ATS-ACC-BKS-004-03, ATS-ACC-BKS-004-11, TST-ACC-BKS-053, TST-ACC-BKS-085]
    statement: >-
      Rate resolution returns the latest quote on or before the requested date,
      defaulting to the workspace’s current date, with source and pricing
      settings or no quote; Commerce uses the same database answer.
    kind: calculation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.queries.integration.spec.js#answers
      the latest rate on or before the day with the currency’s settings
    origin: none
  - id: BR-CAP-ACC-BKS-004-04
    proved_by: [ATS-ACC-BKS-004-07]
    statement: >-
      Provider rates cannot be edited manually; deletion is permitted, so
      correction requires deletion followed by another fill.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-accounting:modules/books/services/persistence/exchange-rates.ts:updateExchangeRate
    origin: 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-4'
  - id: BR-CAP-ACC-BKS-004-05
    proved_by: [ATS-ACC-BKS-004-04, TST-ACC-BKS-084]
    statement: >-
      Provider filling makes one request outside the database transaction for
      missing quotes among selected provider currencies, skips existing dated
      quotes, reports unpriced currencies and stores quotes on the provider’s
      date.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.integration.spec.js#skips
      a currency whose day already holds a rate, reports one the provider did
      not price, and stores a quote on the provider’s own earlier day
    origin: 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#P-4'
  - id: BR-CAP-ACC-BKS-004-06
    proved_by: [ATS-ACC-BKS-004-05, TST-ACC-BKS-053, TST-ACC-BKS-084]
    statement: >-
      Filling refuses table-sourced or unsupported currencies and units without
      provider currencies, and writes nothing when the provider is unavailable
      or declines.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.integration.spec.js#refuses
      provider_unavailable and provider_declined, writing nothing
    origin: none
  - id: BR-CAP-ACC-BKS-004-07
    proved_by: [ATS-ACC-BKS-004-06, TST-ACC-BKS-172]
    statement: >-
      Conversion rounds half away from zero at the target currency’s precision,
      while reciprocal rates round half up to ten decimal places.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/books/tests/unit/rules/exchange-rate-rules.spec.js#applies
      the target currency’s exponent exactly: 100.00 USD at 149.37 is 14937 yen
    origin: none
  - id: BR-CAP-ACC-BKS-004-08
    proved_by: []
    statement: >-
      Rate lists order by unit, currency and descending date, support unit,
      currency and inclusive date filters, and provider filling requires
      rate-create permission.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: false
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.queries.integration.spec.js#lists
      the tenant’s rates by unit, currency, newest day first
    origin: none
scenarios:
  - id: SC-CAP-ACC-BKS-004-01
    given: The unit maintains Canadian-dollar quotes manually.
    when: Staff enter today’s and last week’s quotes and resolve yesterday’s rate.
    then: >-
      Resolution returns last week’s quote together with table sourcing and
      calculated-price handling.
    provenance: test
    source: >-
      react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0030.spec.ts#ACT-ACC-0030
  - id: SC-CAP-ACC-BKS-004-02
    given: The development workspace has no available provider connection.
    when: The controller requests a provider fill.
    then: The request reports provider unavailability without saving a quote.
    provenance: test
    source: >-
      services-accounting:modules/books/tests/e2e/exchange-rates.e2e.spec.js#refuses
      the fill 409 provider_unavailable while no platform client exists, writing
      nothing
edge_cases: []
constraints: []
depends_on:
  - CAP-ACC-BKS-002
related:
  - CAP-ACC-BKS-001
journeys:
  - JNY-ACC-BKS-006
open_questions: []
evidence:
  - >-
    Verified source file:
    react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0030.spec.ts
  - 'Verified source file: services-accounting:aws/accounting.service.ts'
  - >-
    Verified source file:
    services-accounting:docs/architecture/accounting-2.0/02-decisions.md
  - >-
    Verified source file:
    services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md
  - >-
    Verified source file:
    services-accounting:docs/milestones/plans/ACCT-M1-plan.md
  - >-
    Verified source file:
    services-accounting:modules/books/db/exchange-rates.table.ts
  - >-
    Verified source file:
    services-accounting:modules/books/services/persistence/exchange-rates.ts
  - >-
    Verified source file:
    services-accounting:modules/books/tests/e2e/exchange-rates.e2e.spec.js
  - >-
    Verified source file:
    services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.integration.spec.js
  - >-
    Verified source file:
    services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.queries.integration.spec.js
  - >-
    Verified source file:
    services-accounting:modules/books/tests/unit/integrations-exchange-rate-provider.spec.js
  - >-
    Verified source file:
    services-accounting:modules/books/tests/unit/rules/exchange-rate-rules.spec.js
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-CRY-001, TC-DAT-001, TC-DAT-002, TC-DAT-004, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-ACC-BKS-01-02]
suites: [TST-ACC-BKS-053, TST-ACC-BKS-084, TST-ACC-BKS-085, TST-ACC-BKS-172]
---

# Maintain dated exchange rates for a unit

## Purpose

Finance staff maintain precise foreign-currency quotes and resolve the rate applicable to a date.

## How It Works

The controller selects a unit and a foreign currency in Exchange Rates. They enter a dated manual quote or ask the provider to fill eligible missing quotes. Stored rates preserve decimal precision. Later pricing and accounting work resolves the quote applicable to its date. A provider failure leaves the configuration unchanged. An existing quote keeps its identifying choices during correction. Provider quotes require a different correction path from manually entered quotes.

## Relationships

The unit’s supported-currency settings govern rate entry, provider filling and missing-price treatment.
