681 lines
62 KiB
Markdown
681 lines
62 KiB
Markdown
# 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** пользователь видит причину, сохраняет черновик и может получить актуальные данные для следующего действия без перезаписи чужого успешного изменения
|