Files
specs/openspec/specs/banking/manual-bank-cards/spec.md
T
codex-bot 3821d9ed58
Specs security / security (push) Successful in 7s
Specs verify / verify (push) Successful in 14s
refactor: group canonical specs by domain and archive account-top-positions (#74)
2026-11-02 21:50:49 +03:00

681 lines
62 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# manual-bank-cards Specification
## Purpose
Позволяет пользователю вручную вести дебетовые банковские карты в рублях:
сохранять операции, видеть независимые балансы, исправлять записи и оценивать
месячные расходы по категориям с учётом возвратов.
## Requirements
### Requirement: Отдельный раздел ручного учёта банковских карт
Система SHALL предоставлять авторизованному пользователю раздел банковских
карт на `/cards` и просмотр отдельной своей карты на `/cards/:id`.
Раздел MUST работать без подключения T-Bank и не зависеть от доступности
инвестиционных данных. Банковские карты MUST NOT становиться брокерскими
счетами, а банковские суммы и операции MUST NOT включаться в инвестиционный
портфель, пассивный доход или прогноз выплат.
Первая версия SHALL поддерживать только ручной учёт дебетовых карт в RUB.
Автоматическая загрузка из банков, кредитные карты, другие валюты, общий
денежный баланс нескольких карт, прогнозы и бюджеты не входят в возможность.
#### Scenario: Работа без инвестиционного подключения
- **WHEN** пользователь с действующей локальной сессией, но без подключения T-Bank, открывает раздел карт
- **THEN** он может просматривать и вести свои банковские карты без перехода к подключению брокера
#### Scenario: Независимость от инвестиционных данных
- **WHEN** пользователь сохраняет банковское пополнение или расход, а инвестиционные данные временно недоступны
- **THEN** банковская операция сохраняется по правилам ручного учёта и не меняет инвестиционные суммы или пассивный доход
### Requirement: Ручное добавление независимых банковских карт
Система SHALL позволять пользователю добавить карту с названием, начальным
остатком и датой начала учёта. Название MUST содержать от 1 до 100 символов
после удаления внешних пробелов. Название банка необязательно и MUST содержать
не более 100 символов после удаления внешних пробелов. Последние четыре цифры
карты необязательны; указанное значение MUST состоять ровно из четырёх цифр.
Полный номер карты, срок действия, PIN и код безопасности не запрашиваются
и не сохраняются как реквизиты этой возможности.
Каждая карта MUST иметь собственную идентичность, баланс и историю независимо
от совпадения банка, названия или последних цифр. Пользователь SHALL иметь
возможность изменить указанные сведения о своей карте с теми же ограничениями.
Финансовые изменения начального остатка и даты начала подчиняются отдельным
требованиям исправления и архива.
#### Scenario: Добавление карты с начальным остатком
- **WHEN** пользователь сохраняет карту с допустимым названием, датой начала учёта и начальным остатком 10 000 ₽
- **THEN** карта появляется в его списке с балансом 10 000 ₽ и без вручную внесённых операций
#### Scenario: Необязательные сведения не заполнены
- **WHEN** пользователь добавляет карту без названия банка и последних четырёх цифр
- **THEN** отсутствие этих сведений не препятствует сохранению карты при корректности обязательных значений
#### Scenario: Несколько карт одного банка
- **WHEN** пользователь добавляет две карты одного банка с одинаковым названием и начальными остатками 1 000 ₽ и 2 000 ₽
- **THEN** система сохраняет две самостоятельные карты с раздельными балансами и историей
#### Scenario: Некорректные сведения карты
- **WHEN** название после удаления внешних пробелов пустое или длиннее 100 символов, название банка длиннее 100 символов либо указанные последние цифры не состоят ровно из четырёх цифр
- **THEN** система отклоняет сохранение, объясняет ошибку соответствующего поля и не создаёт или не изменяет карту
### Requirement: Точные денежные значения и проверка до сохранения
Система MUST принимать денежные значения только в RUB, с точностью не более
двух знаков после десятичного разделителя, без округления введённой суммы.
Сумма расхода, пополнения, перевода или возврата MUST быть строго положительной
и не превышать 92 233 720 368 547 758,07 ₽. Начальный остаток MUST находиться
в диапазоне от −92 233 720 368 547 758,08 ₽ до 92 233 720 368 547 758,07 ₽
включительно; ноль и отрицательный начальный остаток допустимы.
Значение, не представляющее корректную денежную сумму, неподдерживаемая
валюта, лишние десятичные знаки или выход за диапазон MUST приводить
к понятной ошибке без частичного сохранения. Применение ограничения
к каждому денежному вводу MUST NOT приводить к округлению, переполнению
или ограничению точного результата сложения уже сохранённых сумм.
#### Scenario: Нулевой и отрицательный начальный остаток
- **WHEN** пользователь сохраняет отдельные карты с начальными остатками 0 ₽ и −100 ₽
- **THEN** система сохраняет обе карты и показывает соответствующие известные балансы
#### Scenario: Неположительная сумма операции
- **WHEN** пользователь пытается внести операцию с суммой 0 ₽ или −1 ₽
- **THEN** система отклоняет операцию, сохраняя прежние историю, балансы и сводки
#### Scenario: Точность не исправляется округлением
- **WHEN** пользователь пытается сохранить начальный остаток или операцию со значением 1,001 ₽
- **THEN** система сообщает о допустимых двух десятичных знаках и не заменяет введённое значение на 1 ₽
#### Scenario: Границы денежных значений
- **WHEN** пользователь указывает верхнюю допустимую сумму операции или нижний допустимый начальный остаток
- **THEN** сами эти граничные значения принимаются, а выход за соответствующую границу на одну копейку отклоняется без изменения данных
#### Scenario: Некорректная сумма или валюта
- **WHEN** поступает попытка сохранить нечисловую сумму либо сумму в валюте, отличной от RUB
- **THEN** система объясняет ошибку и не сохраняет карту, операцию или изменение существующих данных
### Requirement: Даты начала учёта и ручных операций
Дата начала учёта и дата операции MUST быть существующими календарными датами
без времени. Сегодня определяется по Europe/Moscow. Дата начала учёта
MUST NOT быть позже сегодняшней даты. Начальный остаток означает деньги
до всех вручную внесённых операций начиная с указанной даты включительно.
Дата операции MUST находиться между датой начала учёта затронутой карты
и сегодняшним днём включительно. Для перевода ограничение MUST выполняться
для обеих карт. Ввод прошедших операций внутри этого интервала SHALL быть
доступен. Необязательное описание операции MUST содержать не более
500 символов. Сохранённая календарная дата MUST NOT сдвигаться при смене
часового пояса устройства.
#### Scenario: Операция в первый день учёта
- **WHEN** при начальном остатке 1 000 ₽ на 1 сентября пользователь вносит расход 100 ₽ с датой 1 сентября
- **THEN** расход учитывается после начального остатка и баланс становится 900 ₽
#### Scenario: Прошедшая операция
- **WHEN** пользователь вносит операцию задним числом не раньше начала учёта и не позже сегодняшнего дня
- **THEN** операция сохраняется с указанной датой и изменяет текущий баланс и сводку соответствующего месяца
#### Scenario: Недопустимая дата или описание
- **WHEN** дата не существует, находится до начала учёта или в будущем либо описание длиннее 500 символов
- **THEN** система объясняет ошибку и не сохраняет изменение
#### Scenario: Перевод до начала учёта получателя
- **WHEN** дата перевода допустима для карты-источника, но предшествует началу учёта карты-получателя
- **THEN** перевод отклоняется без изменения обеих карт
#### Scenario: Московская календарная граница
- **WHEN** на устройстве ещё предыдущий день, а в Europe/Moscow уже наступил следующий
- **THEN** допустимая сегодняшняя дата и текущий месяц определяются по Europe/Moscow, а сохранённые даты операций остаются прежними
### Requirement: Баланс определяется начальным остатком и внесёнными операциями
Баланс карты MUST равняться начальному остатку плюс денежное влияние всех
её сохранённых и не удалённых операций: пополнение и возврат увеличивают
баланс, расход уменьшает, перевод уменьшает баланс источника и увеличивает
баланс получателя. Начальный остаток MUST учитываться ровно один раз
и MUST NOT считаться расходом, возвратом или пополнением.
Баланс MUST рассчитываться точно и может быть отрицательным. Недостаточный
учётный остаток MUST NOT препятствовать сохранению корректного расхода
или перевода. Баланс MUST NOT зависеть от выбранного месяца, категории,
показанной страницы истории или нахождения карты в архиве.
Система SHALL представлять баланс как результат ручного учёта и MUST NOT
выдавать его за подтверждённый банком остаток или обещать полноту невнесённых
операций. Отдельной произвольной операции корректировки баланса SHALL NOT быть.
#### Scenario: Последовательные расходы с копейками
- **WHEN** при начальном остатке 1 ₽ сохраняются расходы 0,10 ₽ и 0,20 ₽
- **THEN** баланс составляет ровно 0,70 ₽
#### Scenario: Полное расходование и отрицательный остаток
- **WHEN** при балансе 100 ₽ пользователь сохраняет расход 100 ₽, затем расход 50 ₽
- **THEN** первая операция даёт известный баланс 0 ₽, вторая сохраняется и даёт баланс −50 ₽
#### Scenario: Начальный остаток и страница истории
- **WHEN** при начальном остатке 1 000 ₽ и расходах 100 ₽ и 200 ₽ пользователь повторно открывает карту и видит только расход 200 ₽
- **THEN** текущий баланс остаётся 700 ₽: учитываются обе операции, а начальный остаток не добавляется повторно
#### Scenario: Итог превышает границу отдельного ввода
- **WHEN** на карту с начальным остатком 92 233 720 368 547 758,07 ₽ сохраняется пополнение 0,01 ₽
- **THEN** баланс составляет точно 92 233 720 368 547 758,08 ₽ без переполнения, округления или ограничения верхней границей ввода
### Requirement: Ручное внесение расходов и пополнений
Система SHALL позволять вручную внести расход или пополнение по своей активной
карте с суммой, датой и необязательным описанием. Расход MUST иметь категорию;
если пользователь не выбирает другую, применяется его категория по умолчанию.
После успешного сохранения операция MUST появиться в истории соответствующей
карты и ровно один раз изменить её баланс.
Расход SHALL уменьшать баланс на свою сумму, пополнение SHALL увеличивать.
Категория MUST NOT изменять денежное влияние расхода. Пополнение MUST NOT
включаться в расходы, возвраты или пассивный инвестиционный доход.
#### Scenario: Расход с категорией
- **WHEN** при балансе 10 000 ₽ пользователь сохраняет расход 500 ₽ с категорией «Кафе»
- **THEN** история показывает расход 500 ₽ с этой категорией, а баланс составляет 9 500 ₽
#### Scenario: Категория по умолчанию
- **WHEN** пользователь сохраняет расход, не выбрав другую категорию
- **THEN** расход сохраняется с принадлежащей пользователю категорией по умолчанию и отображается под её текущим названием
#### Scenario: Пополнение карты
- **WHEN** при балансе −500 ₽ пользователь сохраняет пополнение 2 000 ₽
- **THEN** баланс становится 1 500 ₽, история содержит пополнение, а расходы и возвраты не меняются
#### Scenario: Независимость другой карты
- **WHEN** при балансах A 1 000 ₽ и B 2 000 ₽ пользователь сохраняет расход 125,50 ₽ по A
- **THEN** баланс A становится 874,50 ₽, а баланс и история B остаются прежними
### Requirement: Перевод между своими картами как одно действие
Система SHALL позволять одним действием внести перевод между двумя разными
активными картами одного пользователя. Сумма перевода MUST одинаково уменьшать
баланс источника и увеличивать баланс получателя. История каждой стороны
MUST показывать направление и другую карту этого перевода.
Перевод MUST сохраняться, исправляться и удаляться целиком для обеих сторон.
Нельзя сохранять только одну сторону, переводить на ту же или чужую карту.
Перевод MUST NOT увеличивать расходы или возвраты в сводках одной или всех карт.
#### Scenario: Перевод между картами
- **WHEN** при балансах A 5 000 ₽ и B 1 000 ₽ пользователь сохраняет перевод 2 000 ₽ из A в B
- **THEN** баланс A становится 3 000 ₽, баланс B — 3 000 ₽, обе истории показывают стороны одного перевода, а расходы не меняются
#### Scenario: Недостаточный остаток источника
- **WHEN** при балансах A 100 ₽ и B 0 ₽ пользователь сохраняет перевод 150 ₽ из A в B
- **THEN** перевод сохраняется целиком, баланс A становится −50 ₽, а баланс B — 150 ₽
#### Scenario: Недопустимый получатель
- **WHEN** получателем указана та же карта или карта другого пользователя
- **THEN** система отклоняет перевод без изменения историй, балансов и сводок
#### Scenario: Сбой сохранения перевода
- **WHEN** перевод не удаётся сохранить
- **THEN** система не подтверждает успех и не оставляет сохранённой только одну его сторону
### Requirement: Возврат связан с исходным расходом
Возврат SHALL ссылаться на существующий не удалённый расход той же карты.
При внесении возврата пользователь SHALL иметь возможность выбрать исходный
расход карты за любой период учёта, независимо от месяца открытого журнала.
Частичные и несколько возвратов допустимы; сумма всех не удалённых возвратов
MUST NOT превышать текущую сумму исходного расхода. Дата возврата MUST быть
не раньше даты расхода и соответствовать общим ограничениям даты карты.
При редактировании возврата проверка общей возвращённой суммы MUST заменять
его прежнюю сумму новой, не учитывая обе версии одновременно.
Возврат MUST увеличивать баланс карты и наследовать категорию исходного
расхода. Отдельный выбор категории возврата SHALL NOT предоставляться.
Изменение категории исходного расхода MUST менять категорию связанных
возвратов и результаты затронутых месячных сводок, сохраняя их суммы и даты.
#### Scenario: Частичные возвраты
- **WHEN** по расходу 1 000 ₽ пользователь последовательно сохраняет возвраты 300 ₽ и 700 ₽
- **THEN** оба возврата связаны с расходом, увеличивают баланс суммарно на 1 000 ₽ и используют его категорию
#### Scenario: Выбор покупки прошлого месяца
- **WHEN** пользователь открывает сентябрьский журнал и вносит возврат по августовской покупке
- **THEN** форма позволяет найти и выбрать исходный августовский расход этой карты без ограничения сентябрьским журналом
#### Scenario: Исправление полностью возвращённой покупки
- **WHEN** по расходу 500 ₽ сохранён единственный возврат 500 ₽ и пользователь изменяет только описание возврата
- **THEN** исправление сохраняется: общая возвращённая сумма остаётся 500 ₽, а прежняя и новая версии не складываются как два возврата
#### Scenario: Превышение возвращаемой суммы
- **WHEN** при расходе 1 000 ₽ и сохранённом возврате 800 ₽ пользователь пытается сохранить ещё 300 ₽
- **THEN** система отклоняет новый возврат без изменения существующих записей и сумм
#### Scenario: Недопустимая связь или дата возврата
- **WHEN** возврат ссылается на удалённую операцию, операцию другого вида или другой карты либо датирован раньше расхода
- **THEN** система отклоняет возврат без изменения истории, баланса и сводок
#### Scenario: Изменение категории покупки
- **WHEN** пользователь меняет категорию расхода с «Покупки» на «Здоровье», а возврат по нему произошёл в другом месяце
- **THEN** расход и возврат учитываются в категории «Здоровье» в своих месяцах с прежними денежными суммами
### Requirement: Общие для пользователя категории расходов
Система SHALL предоставлять каждому пользователю собственный общий для всех
его карт список категорий: «Продукты», «Кафе», «Такси», «Транспорт», «Жильё»,
«Здоровье», «Покупки», «Развлечения», «Другое». «Другое» является начальной
категорией по умолчанию. Категории MUST быть доступны до первого расхода.
Начальный набор MUST создаваться при успешном добавлении первой карты
пользователя и не дублироваться при добавлении следующих карт.
Пользователь SHALL иметь возможность добавлять и переименовывать любые свои
категории, включая начальные. Название MUST содержать от 1 до 80 символов
после удаления внешних пробелов и быть уникальным среди категорий пользователя
без учёта регистра. Переименование MUST сохранять идентичность категории:
все старые расходы, возвраты и сводки показывают её текущее название.
Переименование категории по умолчанию MUST сохранять её роль.
Удаление категорий и вложенные подкатегории SHALL NOT предоставляться в V1.
#### Scenario: Начальный набор и повторный вход
- **WHEN** пользователь сохраняет первую карту, затем добавляет следующую карту и повторно входит в приложение
- **THEN** после первой карты ему доступен начальный набор категорий до первого расхода, а дальнейшие действия не создают дубликатов
#### Scenario: Категория доступна на всех своих картах
- **WHEN** пользователь создаёт категорию «Питомец»
- **THEN** она доступна для расходов по всем его картам и не появляется у другого пользователя
#### Scenario: Переименование используемой начальной категории
- **WHEN** пользователь переименовывает категорию «Другое» в «Остальное»
- **THEN** прежние записи отображаются как «Остальное», суммы не меняются, а новые расходы без другого выбора используют ту же категорию «Остальное»
#### Scenario: Дублирующееся или недопустимое название
- **WHEN** пользователь пытается добавить « кафе » при существующей «Кафе», сохранить пустое после удаления пробелов название или название длиннее 80 символов
- **THEN** система объясняет ошибку и не изменяет список категорий
### Requirement: Исправление и удаление операций с согласованным пересчётом
Пользователь SHALL иметь возможность изменить сумму, дату, описание
и применимые реквизиты сохранённой операции либо удалить её. Вид операции
и связь возврата с исходным расходом MUST быть неизменны после создания.
Все затронутые карты MUST быть активны; каждое исправление MUST соблюдать
те же денежные, календарные и пользовательские ограничения, что и создание.
Обычный расход или пополнение SHALL быть переносимы на другую свою активную
карту. Расход с не удалёнными возвратами MUST NOT переноситься на другую карту,
удаляться, уменьшаться ниже суммы возвратов или сдвигаться на дату позже
любого связанного возврата. Для такого действия сначала исправляются
или удаляются мешающие возвраты. Возврат остаётся на карте исходного расхода.
Исправление или удаление MUST пересчитать балансы, историю и все затронутые
месячные сводки; изменение даты переносит влияние между соответствующими
месяцами. Исправление сторон перевода MUST учитывать его прежние и новые
карты. Удаление перевода MUST удалять обе стороны одним действием.
Повторное удаление уже удалённой собственной операции SHALL быть
безрезультатным успешным действием без повторного денежного влияния.
#### Scenario: Изменение суммы и месяца расхода
- **WHEN** пользователь меняет расход 100 ₽ за август на 150 ₽ за сентябрь
- **THEN** баланс дополнительно уменьшается на 50 ₽, август перестаёт учитывать 100 ₽, а сентябрь учитывает 150 ₽
#### Scenario: Перенос пополнения на другую карту
- **WHEN** пользователь переносит пополнение 500 ₽ с A на свою активную B с допустимой датой
- **THEN** баланс A уменьшается на 500 ₽, баланс B увеличивается на 500 ₽, а запись относится к B
#### Scenario: Изменение получателя перевода
- **WHEN** перевод 200 ₽ из A в B исправляется на перевод 300 ₽ из A в C и все три карты активны
- **THEN** прежнее влияние на B отменяется, A уменьшается дополнительно на 100 ₽, C увеличивается на 300 ₽, а обе стороны исправленного перевода сохраняются согласованно
#### Scenario: Исправление расхода ограничено возвратами
- **WHEN** для расхода 1 000 ₽ существует возврат 600 ₽ и пользователь пытается удалить расход, перенести его на другую карту, уменьшить до 500 ₽ или сдвинуть позже даты возврата
- **THEN** система отклоняет действие и объясняет необходимость сначала исправить связанные возвраты
#### Scenario: Удаление возврата и затем расхода
- **WHEN** пользователь удаляет все возвраты расхода, а затем сам расход
- **THEN** влияние каждой удалённой записи отменяется в балансе и в её месячной сводке, а ограничение удаления исходного расхода больше не действует
#### Scenario: Удаление перевода из любой стороны
- **WHEN** пользователь удаляет перевод из истории получателя и затем повторяет удаление
- **THEN** обе стороны удаляются при первом действии, денежное влияние отменяется один раз, а повторное удаление ничего не меняет
#### Scenario: Смена вида или исходного расхода
- **WHEN** пользователь пытается превратить расход в пополнение либо связать существующий возврат с другим расходом
- **THEN** изменение отклоняется без подмены вида операции или её исходной связи
### Requirement: Исправление начала ручного учёта
Пользователь SHALL иметь возможность изменить начальный остаток и дату начала
учёта активной карты. Изменение MUST проверять всю затронутую сохранённую
историю, включая стороны переводов и связанные возвраты, и отклоняться
без частичного результата, если после него какая-либо операция нарушает
действующие ограничения. Удалённые операции не ограничивают новую дату.
Изменение начального остатка SHALL пересчитывать текущий баланс, сохраняя
суммы, даты и категории операций. Оно MUST NOT создавать отдельную операцию
или изменять месячные расходы и возвраты. Пропущенное движение SHALL
исправляться внесением реальной операции с допустимой датой.
#### Scenario: Исправление начального остатка
- **WHEN** при начальном остатке 1 000 ₽ и расходах 300 ₽ пользователь меняет начальный остаток на 1 200 ₽
- **THEN** баланс становится 900 ₽, история операций не пополняется корректировкой, а месячные расходы остаются 300 ₽
#### Scenario: Новое начало противоречит истории
- **WHEN** пользователь переносит дату начала учёта с 1 на 10 сентября, а по карте сохранён расход или перевод от 5 сентября
- **THEN** система отклоняет изменение и сохраняет прежние дату, баланс и всю историю
### Requirement: Архив карт сохраняет историю
Пользователь SHALL иметь возможность архивировать свою карту при любом
балансе, включая положительный, нулевой и отрицательный, и вернуть её
из архива. Архивация MUST NOT менять деньги или операции. Обычный список
SHALL показывать активные карты; переключатель архива SHALL открывать
архивные карты и их историю.
Пока карта архивна, система MUST запрещать создание, редактирование
и удаление затрагивающих её операций, а также изменение начального остатка
и даты начала учёта. Для исправления или новой операции карту сначала
возвращают из архива. Перевод или возврат MUST требовать активного состояния
всех затронутых карт. Изменение описательных сведений карты само по себе
не является финансовой операцией.
Архивная карта MUST продолжать участвовать в исторических сводках «Все карты».
Безвозвратное удаление карты SHALL NOT предоставляться в первой версии.
#### Scenario: Архивирование с ненулевым остатком
- **WHEN** пользователь архивирует карту с балансом −500 ₽ и сохранённой историей
- **THEN** карта скрывается из обычного списка, остаётся доступной через переключатель архива с теми же балансом и историей и сохраняет вклад в сводки всех карт
#### Scenario: Финансовое действие по архивной карте
- **WHEN** пользователь пытается внести, исправить или удалить операцию либо изменить начало учёта архивной карты
- **THEN** система отклоняет действие без изменения данных и объясняет необходимость вернуть карту из архива
#### Scenario: Возврат или перевод затрагивает архив
- **WHEN** возврат относится к расходу архивной карты либо хотя бы одна прежняя или новая сторона изменяемого перевода архивна
- **THEN** финансовое действие отклоняется до возвращения всех затронутых карт из архива
#### Scenario: Возвращение карты
- **WHEN** пользователь возвращает карту из архива
- **THEN** она снова видна в обычном списке с сохранёнными данными и допускает финансовые действия по общим правилам
### Requirement: Месячный журнал и сводка расходов по категориям
Система SHALL показывать историю и месячную сводку выбранной карты, а также
месячную сводку всех банковских карт пользователя. По умолчанию выбирается
текущий месяц Europe/Moscow; пользователь SHALL иметь возможность явно выбрать
другой месяц. История карты и её сводка MUST использовать один выбранный месяц.
Операции SHALL отображаться по убыванию календарной даты со стабильным порядком
внутри даты при повторном чтении неизменившихся данных и доступом к следующим
страницам истории.
Сводка MUST показывать расходы, возвраты и итог после возвратов, а также
соответствующие суммы по категориям. Расход входит в месяц собственной даты,
возврат — в месяц даты возврата, даже если покупка была раньше.
Итог MUST равняться расходам минус возвраты и может быть отрицательным.
Начальные остатки, пополнения и переводы MUST NOT включаться в эти суммы.
Сводка MUST учитывать все подходящие не удалённые записи, а не только
текущую страницу истории. «Все карты» MUST включать активные и архивные карты
владельца. Выбор месяца и охвата MUST NOT изменять текущие балансы.
При отсутствии расходов и возвратов SHALL показываться известные нули.
#### Scenario: Месяц по умолчанию и переключение
- **WHEN** пользователь открывает карту в сентябре по Europe/Moscow и затем выбирает август
- **THEN** сначала история и сводка относятся к сентябрю, после выбора обе относятся к августу, а текущий баланс остаётся прежним
#### Scenario: Сводка расходов и переводов
- **WHEN** в месяце сохранены расходы «Кафе» 500 ₽ и «Такси» 300 ₽, пополнение 2 000 ₽ и перевод 1 000 ₽ без возвратов
- **THEN** сводка показывает расходы 800 ₽, возвраты 0 ₽, итог 800 ₽ и категории 500 ₽ и 300 ₽ без включения пополнения или перевода
#### Scenario: Возврат покупки прошлого месяца
- **WHEN** расход «Покупки» 1 000 ₽ совершён в августе, возврат 600 ₽ — в сентябре и других операций этих видов нет
- **THEN** август показывает расходы 1 000 ₽, возвраты 0 ₽ и итог 1 000 ₽, а сентябрь — расходы 0 ₽, возвраты 600 ₽ и итог −600 ₽ в той же категории
#### Scenario: Все карты включают архивные
- **WHEN** расходы активной A за месяц составили 800 ₽, архивной B — 200 ₽, а между ними ранее был перевод 1 000 ₽
- **THEN** общая сводка показывает расходы 1 000 ₽ и не добавляет перевод, независимо от переключателя архива в списке карт
#### Scenario: Несколько страниц и равные даты
- **WHEN** операции выбранного месяца занимают несколько страниц и часть операций имеет одинаковую дату
- **THEN** пользователь может просмотреть все страницы в стабильном порядке, а сводка и баланс учитывают все соответствующие записи независимо от открытой страницы
### Requirement: Изоляция пользовательских финансовых данных
Карты, категории, операции и связи между ними MUST принадлежать локальному
пользователю WealthLedger. Система MUST предоставлять чтение, создание,
изменение, удаление, архив и восстановление только владельцу с действующей
сессией. Идентификатор сам по себе MUST NOT подтверждать доступ.
Проверка владельца MUST распространяться на каждую упомянутую карту,
категорию и операцию, включая обе стороны перевода, исходный расход возврата
и новые связи при редактировании. Отказ MUST NOT раскрывать чужие финансовые
сведения или менять данные любой стороны. Сводки MUST включать только
данные текущего пользователя.
#### Scenario: Нет действующей сессии
- **WHEN** пользователь без действующей сессии пытается прочитать карты, сводку или изменить финансовые данные
- **THEN** система отказывает в доступе без раскрытия и изменения сохранённых данных
#### Scenario: Чужая карта или категория
- **WHEN** пользователь обращается к чужой карте или пытается назначить своей операции чужую категорию
- **THEN** система отклоняет действие, не раскрывает чужие сведения и не меняет баланс, историю или категории
#### Scenario: Чужая связь перевода или возврата
- **WHEN** пользователь подставляет чужую карту в перевод или чужой расход в возврат при создании либо редактировании
- **THEN** система отклоняет всё действие без сохранения частичного результата и без раскрытия данных другого пользователя
#### Scenario: Чужие исправления и архив
- **WHEN** пользователь пытается изменить или удалить чужую операцию, переименовать чужую категорию либо архивировать или восстановить чужую карту
- **THEN** система отказывает и сохраняет все данные владельца без изменений
### Requirement: Согласованные изменения и защита повторной отправки
Успешное финансовое изменение MUST согласованно сохранять операции, связи,
денежное влияние и пересчитанные результаты для всех затронутых карт.
Система MUST NOT подтверждать успех несохранённого действия или оставлять
частичный результат. Независимые одновременные операции MUST сохранять
влияние каждой успешной записи, не затирая друг друга.
Повторная отправка одного намерения создать карту или операцию с тем же
содержанием MUST приводить не более чем к одному соответствующему результату
и однократному учёту начального остатка или денежного влияния операции.
Иное содержание при повторном использовании того же намерения MUST приводить
к конфликту без изменения данных. После исправления или удаления результата
поздняя повторная отправка исходного создания MUST NOT восстанавливать
старые значения, удалённую запись или её денежное влияние.
Ответ на повтор MUST показывать актуальное состояние ранее созданной карты
или операции либо сообщать, что операция удалена, не выдавая старые данные
за текущий результат. Уже сохранённое намерение MUST NOT проверяться
как новая финансовая запись и повторно расходовать доступную сумму возврата.
Архивирование ранее созданной карты MUST NOT превращать повтор её создания
в создание другой карты или возвращение из архива.
Редактирование или удаление существующей операции и изменение карты,
основанные на её устаревшем состоянии, MUST завершаться понятным конфликтом
без перезаписи более свежих изменений. Повторное удаление уже удалённой
операции не считается перезаписью и остаётся безрезультатным действием.
Проверки суммы возвратов, состояния архива, дат и других ограничений
MUST сохраняться при одновременных действиях.
#### Scenario: Повтор после потерянного ответа
- **WHEN** создание расхода 100 ₽ успешно сохранено, ответ потерян и пользователь повторяет то же намерение с тем же содержанием
- **THEN** история содержит один расход, баланс изменён один раз, а повтор не создаёт дубликат
#### Scenario: Повтор создания карты после архивации
- **WHEN** карта с начальным остатком 1 000 ₽ создана, ответ потерян, карта затем архивирована и приходит повтор её исходного создания
- **THEN** система показывает актуальную архивную карту, не создаёт вторую карту, не дублирует начальный остаток и не возвращает карту из архива
#### Scenario: Повтор полного возврата
- **WHEN** возврат 500 ₽ полностью покрывает расход 500 ₽, успешно сохранён, но ответ потерян и пользователь повторяет то же намерение создания возврата
- **THEN** система возвращает сохранённый возврат без новой записи, повторного изменения баланса или ошибки о превышении суммы покупки
#### Scenario: Изменённое содержание того же намерения
- **WHEN** после сохранения расхода 100 ₽ повтор того же намерения содержит сумму 200 ₽
- **THEN** система сообщает о конфликте и сохраняет исходный результат без дополнительной операции
#### Scenario: Позднее создание после исправления или удаления
- **WHEN** сохранённый расход 100 ₽ исправлен на 150 ₽ или удалён, после чего приходит повтор его исходного создания
- **THEN** система показывает актуальный расход 150 ₽ либо сообщает об удалении; прежняя сумма или удалённая запись не восстанавливаются и дополнительного денежного влияния нет
#### Scenario: Два независимых расхода
- **WHEN** при балансе 1 000 ₽ расходы 100 ₽ и 200 ₽ успешно сохраняются одновременно как разные намерения
- **THEN** история содержит оба расхода, а баланс составляет 700 ₽
#### Scenario: Два возврата превышают покупку вместе
- **WHEN** для расхода 1 000 ₽ одновременно сохраняются независимые возвраты 600 ₽ и 600 ₽
- **THEN** система не допускает общей сохранённой суммы 1 200 ₽: конфликтующее действие отклоняется без частичного результата
#### Scenario: Исправление по устаревшим данным
- **WHEN** операция или карта изменилась после того, как пользователь начал её редактирование, а он отправляет изменение прежнего состояния
- **THEN** система сообщает о конфликте, сохраняет более свежие данные и позволяет получить актуальное состояние для нового исправления
#### Scenario: Удаление по устаревшим данным
- **WHEN** пользователь пытается удалить существующую операцию, которую другой успешный запрос уже изменил
- **THEN** система отклоняет устаревшее удаление без потери более свежего исправления
#### Scenario: Архивирование одновременно с финансовым изменением
- **WHEN** архивирование карты пересекается с созданием или исправлением затрагивающей её операции
- **THEN** результаты соответствуют последовательному выполнению допустимых действий и не допускают финансовую запись, выполненную после вступившего в силу архива
### Requirement: Сохранённый ручной учёт переживает повторный вход и перезапуск
Успешно сохранённые карты, начальные остатки, даты начала, операции,
категории, архивное состояние и связи возвратов и переводов MUST оставаться
доступными владельцу после обновления страницы, выхода, повторного входа
и обычного перезапуска приложения. Эти действия MUST NOT создавать
дубликаты, восстанавливать удалённые операции или сбрасывать балансы.
Защита повторной отправки сохранённых намерений MUST сохранять действие
после этих переходов.
#### Scenario: Повторный вход
- **WHEN** пользователь выходит и снова входит после сохранения карты с начальным остатком 1 000 ₽ и расходом 100 ₽
- **THEN** он видит ту же карту, дату начала, расход с его текущей категорией и баланс 900 ₽
#### Scenario: Перезапуск с переводом, возвратом и архивом
- **WHEN** приложение перезапускается после сохранения переводов, связанных возвратов, переименования категорий и архивирования карты
- **THEN** владелец получает прежние связи, названия, архивное состояние и рассчитанные суммы без дубликатов или потери истории
#### Scenario: Повтор создания после перезапуска
- **WHEN** после перезапуска повторяется намерение создания ранее сохранённой, а затем удалённой операции
- **THEN** удалённая операция и её денежное влияние не восстанавливаются
### Requirement: Доступные формы и различимые состояния без ложного успеха
Раздел SHALL различать отсутствие карт, отсутствие операций за месяц,
известный нулевой баланс, загрузку и ошибку получения данных. Ошибка
MUST NOT подменяться пустым списком или нулевой суммой. Пользователь SHALL
иметь возможность повторить неуспешную загрузку.
Формы карт, операций, исправлений и категорий MUST иметь доступные названия
полей и действий, управление с клавиатуры и понятные ошибки, связанные
с соответствующим полем или действием. Обязательность, направление денег
и ошибки MUST NOT передаваться только цветом.
Во время сохранения, ошибки и конфликта введённый черновик MUST оставаться
доступным до явного закрытия или успешного завершения. Пока результат
сохранения не подтверждён, система MUST NOT показывать введённую сумму
как подтверждённый новый баланс или выдавать действие за успешное.
При неопределённом результате после потери ответа повтор SHALL использовать
защиту того же намерения создания. После подтверждённого изменения
отображаемые история, балансы и сводки MUST согласовываться с сохранёнными
данными всех затронутых карт.
#### Scenario: Пустой список
- **WHEN** раздел успешно загружен и у пользователя ещё нет карт
- **THEN** система показывает штатное пустое состояние с доступным действием добавления карты
#### Scenario: Нет операций при ненулевом балансе
- **WHEN** в выбранном месяце нет операций, но текущий баланс карты составляет 10 000 ₽
- **THEN** история сообщает об отсутствии операций за месяц, сводка содержит нули, а текущий баланс остаётся 10 000 ₽
#### Scenario: Ошибка загрузки
- **WHEN** загрузка карт, истории или сводки завершается ошибкой
- **THEN** система показывает ошибку и действие повтора, не выдавая её за отсутствие данных или нулевой результат
#### Scenario: Доступный ввод и проверка
- **WHEN** пользователь управляет формой с клавиатуры или средством чтения экрана и вводит некорректную сумму
- **THEN** названия полей и действий доступны, ошибка суммы определима без цвета, а введённые значения остаются для исправления
#### Scenario: Ожидание и неопределённый результат
- **WHEN** пользователь отправляет перевод и ответ ещё не получен либо потерян
- **THEN** черновик сохраняется, неподтверждённые балансы не выдаются за сохранённые, а повтор того же создания не порождает второй перевод
#### Scenario: Ошибка или конфликт исправления
- **WHEN** сохранение исправления отклоняется из-за ошибки или устаревшего состояния
- **THEN** пользователь видит причину, сохраняет черновик и может получить актуальные данные для следующего действия без перезаписи чужого успешного изменения