Прямой ответ
Сумма хранится вместе с кодом валюты, а пересчёт выполняется по курсу на дату операции, поэтому значение однозначно относится к валюте и моменту.
| Параметр | Что определяет |
|---|---|
| Код валюты | Валюту, к которой относится сумма |
| Разрядность | Число разменных единиц в основной единице |
| Курс на дату | Значение для пересчёта в момент операции |
| Валюта учёта | Валюту, в которой ведётся отчётность |
from decimal import Decimal
amount = Decimal("100.00")
rate = Decimal("92.5") # курс на дату операции
converted = (amount * rate).quantize(Decimal("0.01"))
Пересчёт выполняется по курсу на дату операции, поэтому сумма в валюте учёта соответствует моменту совершения операции. О хранении сумм — в статье про деньги в приложении.
Код валюты и разрядность
Код валюты определяет валюту суммы, а разрядность — число разменных единиц, поэтому хранение требует обоих параметров.
Код валюты. Сумма сопровождается кодом, поэтому значение однозначно относится к одной валюте и не смешивается с другими.
Разрядность валюты. Число разменных единиц в основной единице различается между валютами, поэтому разрядность хранится либо определяется по коду.
Проверка при операции. Операция над суммами проверяет совпадение кодов, поэтому сложение сумм в разных валютах отклоняется.
Практическое следствие: сумма без кода валюты складывается с суммой в другой валюте, поэтому результат не имеет смысла, а ошибка не обнаруживается.
Отдельный аспект — разрядность при хранении в минимальных единицах. Сумма в минимальных единицах хранится целым числом, поэтому разрядность определяет, сколько разрядов соответствует основной единице. Разрядность, не сохранённая вместе с суммой, делает значение неоднозначным.
Курс на дату и пересчёт сумм
Курс на дату операции определяет значение для пересчёта, поэтому сумма в валюте учёта соответствует моменту совершения операции.
Курс на дату. Значение курса фиксируется на дату операции, поэтому пересчёт не зависит от изменения курса после операции.
Хранение курса. Курс сохраняется вместе с операцией, поэтому пересчёт воспроизводим и не требует обращения к истории курса.
Валюта учёта. Сумма пересчитывается в валюту отчётности, поэтому отчёт формируется в одной валюте.
Практическое следствие: пересчёт по текущему курсу вместо курса на дату операции меняет сумму задним числом, поэтому отчётность за прошлый период не воспроизводится.
Валюта учёта и согласованность расчёта
Валюта учёта определяет валюту отчётности, поэтому пересчёт выполняется до формирования отчёта, а не при чтении.
Пересчёт до отчёта. Сумма пересчитывается в валюту учёта при формировании отчёта, поэтому значение фиксируется в документе.
Согласованность курса. Все операции периода пересчитываются по курсу на дату операции, поэтому итог соответствует моменту каждой операции.
Проверка итога. Сумма в валюте учёта сравнивается с суммой в исходной валюте по курсу, поэтому расхождение указывает на неверный курс.
Практическое следствие: пересчёт при чтении отчёта по текущему курсу даёт значение, отличающееся от зафиксированного в документе, поэтому отчёт не воспроизводится.
О правиле округления при пересчёте — в статье про округление денежных значений; о сверке сумм — в статье про сверку платежей.
Практика: работа с валютами и курсами
1. Храните код валюты вместе с суммой. Значение однозначно относится к валюте, поэтому смешение сумм исключается.
2. Фиксируйте разрядность валюты. Число разменных единиц определяет представление суммы в минимальных единицах.
3. Сохраняйте курс на дату операции. Пересчёт воспроизводим и не зависит от изменения курса после операции.
4. Пересчитывайте по курсу на дату, а не по текущему. Сумма за прошлый период не меняется задним числом.
5. Определяйте валюту учёта для отчётности. Отчёт формируется в одной валюте, поэтому пересчёт выполняется до формирования.
6. Округляйте после пересчёта. Промежуточное значение сохраняет точность, поэтому итог не накапливает погрешность.
7. Проверяйте итог в валюте учёта против исходной суммы. Расхождение указывает на неверный курс либо на ошибку пересчёта.
Рекомендации:
- Не пересчитывайте по текущему курсу для прошлых периодов. Сумма меняется задним числом, поэтому отчётность не воспроизводится.
- Учитывайте разрядность при хранении в минимальных единицах. Разрядность, не сохранённая с суммой, делает значение неоднозначным.
- Сопоставляйте курс с датой операции. Курс на дату фиксируется вместе с операцией, поэтому пересчёт воспроизводим.
- Фиксируйте валюту учёта в настройках отчётности. Отчёт формируется в одной валюте, поэтому пересчёт выполняется единообразно.
- Пересматриваем набор валют при добавлении новой. Новая валюта может требовать иной разрядности и источника курса.
Практический сценарий: отчёт за прошлый месяц менялся при повторном формировании
Контекст: отчёт за прошлый месяц формировался повторно и показывал иные суммы, при этом операции не изменялись.
- Диагностика. Проверка показала, что пересчёт сумм выполнялся по текущему курсу валюты. Курс изменился после совершения операций, поэтому пересчёт давал иное значение при каждом формировании отчёта.
- Причина. Курс на дату операции не сохранялся, поэтому пересчёт использовал текущее значение вместо значения на момент операции.
- Действия. (а) Добавили сохранение курса на дату операции вместе с суммой, чтобы пересчёт использовал зафиксированное значение; (б) пересчитали отчёт за прошлый месяц и убедились, что значение не меняется при повторном формировании; (в) зафиксировали валюту учёта в настройках отчётности, чтобы пересчёт выполнялся единообразно; (г) перенесли округление в конец пересчёта, чтобы промежуточные значения сохраняли точность; (д) добавили проверку итога в валюте учёта против суммы в исходной валюте, чтобы расхождение обнаруживалось.
- Результат. Отчёт за прошлый период воспроизводится, поскольку пересчёт выполняется по курсу на дату операции.
Вывод: пересчёт по текущему курсу меняет суммы прошлых периодов задним числом, поэтому отчёт не воспроизводится при повторном формировании. Сохранение курса на дату операции и пересчёт по зафиксированному значению делают отчётность стабильной.
FAQ
Зачем хранить код валюты вместе с суммой? Сумма без кода валюты складывается с суммой в другой валюте, поэтому результат не имеет смысла, а ошибка не обнаруживается.
Какой курс использовать для пересчёта? Курс на дату операции. Пересчёт по текущему курсу меняет суммы прошлых периодов задним числом.
Что такое валюта учёта? Валюта, в которой ведётся отчётность. Суммы пересчитываются в валюту учёта при формировании отчёта.
Почему важна разрядность валюты? Число разменных единиц в основной единице различается между валютами, поэтому разрядность определяет представление суммы.
Источники: PostgreSQL money type, PostgreSQL numeric types, Python decimal — ссылки в шапке.