openspec: архив aurora-1839-cell-memo и aurora-1984-pd-standalone-projects
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4dc111a0ce
commit
f0470313c1
91
openspec/specs/form4-programs-projects/spec.md
Normal file
91
openspec/specs/form4-programs-projects/spec.md
Normal file
@ -0,0 +1,91 @@
|
||||
# form4-programs-projects Specification
|
||||
|
||||
## Purpose
|
||||
Программы и проекты на листах AHR/CAP/OPER формы 4 (FORM_4, «Проектная деятельность»): как они создаются, переименовываются и удаляются, где стоят в листе, как участвуют в итогах и выгрузке, как подписаны и как к проекту добавляется расход.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Проект создаётся в программе или прямо в разделе
|
||||
Пользователь с правом добавлять строки в раздел листа SHALL иметь возможность создать проект как внутри программы этого раздела, так и прямо в разделе, без программы. Название SHALL быть непустым после обрезки пробелов. Права SHALL проверяться так же, как при создании программы в этом разделе.
|
||||
|
||||
#### Scenario: Проект в разделе
|
||||
- **WHEN** на листе CAP выделена строка раздела (или ничего не выделено) и пользователь создаёт проект «П1»
|
||||
- **THEN** «П1» появляется в разделе CAP без программы у всех подключённых к листу пользователей без перезагрузки
|
||||
- **AND** после перезагрузки страницы «П1» стоит на том же месте
|
||||
|
||||
#### Scenario: Проект в программе — как раньше
|
||||
- **WHEN** выделена программа «Пр» и пользователь создаёт проект
|
||||
- **THEN** проект создаётся внутри «Пр»
|
||||
|
||||
#### Scenario: Выделен проект или расход
|
||||
- **WHEN** выделен проект или строка расхода и пользователь создаёт проект
|
||||
- **THEN** новый проект создаётся там же, где выделенный проект: в той же программе или прямо в разделе
|
||||
|
||||
#### Scenario: Пустое название
|
||||
- **WHEN** пользователь вводит название из одних пробелов
|
||||
- **THEN** проект не создаётся
|
||||
|
||||
### Requirement: Место самостоятельного проекта в листе
|
||||
Самостоятельный проект SHALL выводиться строкой ITEM непосредственно под ROOT своего раздела, без строки GROUP над ним; его расходы — строками INPUT под ним. На уровне под ROOT программы и самостоятельные проекты SHALL идти в порядке создания (по id записи `form4_project`). Проекты внутри программы и их расходы SHALL выводиться как раньше.
|
||||
|
||||
#### Scenario: Вперемешку в порядке создания
|
||||
- **WHEN** в разделе последовательно созданы программа «А», самостоятельный проект «Б», программа «В»
|
||||
- **THEN** под ROOT раздела строки идут в порядке «А» (GROUP), «Б» (ITEM), «В» (GROUP)
|
||||
|
||||
#### Scenario: Пустой раздел
|
||||
- **WHEN** в разделе нет ни одного расхода, но есть самостоятельный проект
|
||||
- **THEN** проект виден в листе так же, как видна пустая программа
|
||||
|
||||
### Requirement: Суммы самостоятельного проекта
|
||||
Суммы расходов самостоятельного проекта SHALL входить в строку ITEM проекта и в ROOT раздела ровно один раз. Лист SHALL NOT содержать строку GROUP без программы (`program_id` пустой). Ответ на правку ячейки расхода самостоятельного проекта SHALL содержать строку расхода, ITEM проекта и ROOT раздела.
|
||||
|
||||
#### Scenario: Итог раздела
|
||||
- **WHEN** в разделе есть программа с расходом 100 и самостоятельный проект с расходом 50 в `plan.q1`
|
||||
- **THEN** ROOT раздела показывает 150 в `plan.q1`, ITEM самостоятельного проекта — 50
|
||||
|
||||
#### Scenario: Правка расхода
|
||||
- **WHEN** пользователь меняет `plan.q1` у расхода самостоятельного проекта
|
||||
- **THEN** ответ содержит этот расход, ITEM проекта и ROOT раздела с пересчитанными суммами и не содержит GROUP
|
||||
|
||||
### Requirement: Расходы самостоятельного проекта
|
||||
К самостоятельному проекту SHALL можно добавлять и удалять расходы так же, как к проекту в программе.
|
||||
|
||||
#### Scenario: Добавление расхода
|
||||
- **WHEN** выделен самостоятельный проект и пользователь добавляет расход по статье
|
||||
- **THEN** расход появляется под этим проектом, суммы проекта и раздела пересчитываются
|
||||
|
||||
### Requirement: Переименование программы и проекта
|
||||
Пользователь с правом изменения SHALL иметь возможность переименовать выделенную программу или проект (в программе и самостоятельный). Окно переименования SHALL открываться с текущим названием. Пустое название и название из одних пробелов SHALL NOT сохраняться. При ошибке сервера окно SHALL оставаться открытым с введённым текстом и показывать понятное сообщение. Отмена SHALL оставлять прежнее название. Переименование SHALL NOT менять состав проектов, расходов и суммы.
|
||||
|
||||
#### Scenario: Успешное переименование
|
||||
- **WHEN** пользователь переименовывает программу «А» в «А2»
|
||||
- **THEN** у всех подключённых пользователей строка показывает «А2», после перезагрузки тоже
|
||||
|
||||
#### Scenario: Ошибка
|
||||
- **WHEN** сервер отказывает в переименовании (например, нет прав)
|
||||
- **THEN** окно остаётся открытым с введённым названием и показывает причину
|
||||
|
||||
### Requirement: Удаление самостоятельного проекта
|
||||
Пустой самостоятельный проект SHALL удаляться с подтверждением, как проект в программе (AURORA-1978). Проект с расходами SHALL NOT удаляться, пользователь видит причину.
|
||||
|
||||
#### Scenario: Удаление пустого
|
||||
- **WHEN** пользователь удаляет пустой самостоятельный проект и подтверждает
|
||||
- **THEN** проект исчезает из листа у всех подключённых, ROOT раздела пересчитывается
|
||||
|
||||
### Requirement: Подпись типа в третьем столбце
|
||||
В третьем столбце листов AHR/CAP/OPER формы 4 строка программы SHALL быть подписана «программа», строка проекта (в программе и самостоятельного) — «проект». Строки ROOT и расходов SHALL показывать код, как раньше. Подпись SHALL совпадать на экране и в выгрузке Excel.
|
||||
|
||||
#### Scenario: Подписи
|
||||
- **WHEN** открыт лист CAP с программой, проектом в ней и самостоятельным проектом
|
||||
- **THEN** напротив программы «программа», напротив обоих проектов «проект», у расходов — код статьи
|
||||
|
||||
### Requirement: Выбор расхода для проекта
|
||||
Окно выбора расхода SHALL показывать варианты в виде «<номер статьи> — <название>», упорядоченные по номеру статьи с учётом числовых частей (например, 2 раньше 10). Поиск SHALL находить вариант по номеру и по названию. Выбор SHALL добавлять расход именно по выбранной статье.
|
||||
|
||||
#### Scenario: Сортировка
|
||||
- **WHEN** среди статей есть номера «3.2.» и «3.10.»
|
||||
- **THEN** «3.2.» стоит выше «3.10.»
|
||||
|
||||
#### Scenario: Поиск по номеру
|
||||
- **WHEN** пользователь вводит «3.01»
|
||||
- **THEN** в списке остаются статьи, номер которых содержит «3.01»
|
||||
203
openspec/specs/sheet-table/spec.md
Normal file
203
openspec/specs/sheet-table/spec.md
Normal file
@ -0,0 +1,203 @@
|
||||
# sheet-table Specification
|
||||
|
||||
## Purpose
|
||||
Таблица листа формы (`web/src/components/RealtimeTable`). Здесь описано, как быстро она прокручивается, как остаются корректными закреплённые колонки, подсветки и состояние ячеек при мемоизации, и какой контракт обязана соблюдать любая правка таблицы.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Шаг прокрутки листа укладывается в 25 мс главного потока
|
||||
|
||||
Прокрутка таблицы листа SHALL стоить не больше 25 мс главного потока на один шаг колеса 200 px на листе FORM_2/AHR.
|
||||
|
||||
Условия замера:
|
||||
- стенд профилирования `perf/` на данных масштаба 1 (`results/manifest-s1.json`, лист до `grow`, 1154 строки);
|
||||
- роль EXECUTOR_RF, prod-сборка, CPU 1×, без троттлинга;
|
||||
- метрика — медиана `main_thread_ms_per_step` по трём повторам сценария `perf/e2e/scenarios/scroll.spec.ts` по оси `y`.
|
||||
|
||||
Горизонтальная прокрутка (ось `x`) того же листа MUST быть не медленнее базы. База — ветка AURORA-1837, на которой стоит эта задача.
|
||||
|
||||
Замер — на той же машине Apple M4, на которой снят отчёт профилирования, пороги абсолютные.
|
||||
|
||||
Пороги этого требования и остальных требований с замерами — цель, а не условие слияния. Цифры до и после SHALL быть приведены в PR. Если цель не достигнута, SHALL быть заведена follow-up issue с разбором того, что съедает остаток. Слияние блокируют только требования корректности.
|
||||
|
||||
#### Scenario: Недобор до цели оформляется follow-up
|
||||
- **WHEN** замер после изменения показывает медиану шага больше 25 мс или регрессию относительно базы
|
||||
- **THEN** PR вливается по зелёным тестам и ручному регрессу, цифры приведены в PR
|
||||
- **AND** заведена follow-up issue с разбором, какие работы главного потока занимают остаток
|
||||
|
||||
#### Scenario: Вертикальная прокрутка FORM_2/AHR после изменения
|
||||
- **WHEN** на стенде прогоняется `PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_TRACE=0 PERF_REPEATS=3 npx playwright test scenarios/scroll.spec.ts` на сборке этой ветки
|
||||
- **THEN** медиана `main_thread_ms_per_step` по трём повторам не больше 25 мс
|
||||
|
||||
#### Scenario: Горизонтальная прокрутка не хуже базы
|
||||
- **WHEN** тот же сценарий прогоняется с `PERF_AXES=x` на сборке ветки AURORA-1837 и на сборке этой ветки на тех же данных
|
||||
- **THEN** медиана `main_thread_ms_per_step` на этой ветке не больше, чем на ветке AURORA-1837
|
||||
- **AND** обе цифры приведены в описании PR
|
||||
|
||||
### Requirement: Прокрутка не перерисовывает ячейки, оставшиеся на экране
|
||||
|
||||
Ячейка таблицы листа, которая при прокрутке осталась в видимой области и у которой не изменились данные строки и определение колонки, SHALL NOT перерисовываться. Перерисовываются только ячейки, которые появились на экране. Строки таблицы и служебные колонки (раскрытие строк) перерисовываться могут.
|
||||
|
||||
Числовая цель по счётчику рендеров не блокирует слияние (см. требование «Шаг прокрутки…»). Неизменность вида ячеек при прокрутке — требование корректности.
|
||||
|
||||
#### Scenario: Счётчик рендеров ячеек на шаг падает
|
||||
- **WHEN** на профилирующей сборке (`:8081`) прогоняется `PERF_HOT_MODE=react` `perf/e2e/scenarios/hotspots.spec.ts` для FORM_2/AHR по оси `y`
|
||||
- **THEN** число рендеров `ForwardRef(Styled(td))` на шаг кратно меньше 1 696, порядка числа ячеек, появившихся на экране за шаг
|
||||
- **AND** цифры до и после приведены в описании PR
|
||||
|
||||
#### Scenario: Вид ячеек при прокрутке не меняется
|
||||
- **WHEN** пользователь прокручивает лист вверх, вниз и вбок и возвращается к исходной позиции
|
||||
- **THEN** каждая видимая ячейка показывает то же значение, цвет, подсветку и признаки, что и до прокрутки
|
||||
|
||||
### Requirement: Закреплённые колонки остаются на своих местах при изменении раскладки
|
||||
|
||||
После любого изменения раскладки закреплённых колонок таблица листа SHALL показывать закреплённые колонки примыкающими друг к другу, без наложения и без зазоров. Видимые ячейки закреплённых колонок SHALL оставаться на месте при горизонтальной прокрутке.
|
||||
|
||||
Изменения раскладки: закрепление колонки, открепление, скрытие закреплённой колонки, показ скрытой закреплённой колонки, смена ширины закреплённой колонки.
|
||||
|
||||
Пока пользователь тянет ширину закреплённой колонки, соседние закреплённые колонки MAY отставать от неё. После отпускания кнопки мыши раскладка MUST стать корректной без дополнительных действий пользователя.
|
||||
|
||||
#### Scenario: Закрепление колонки
|
||||
- **WHEN** пользователь выделяет колонку в строке букв и нажимает «Закрепить область»
|
||||
- **THEN** колонка встаёт сразу после ранее закреплённых и остаётся на месте при горизонтальной прокрутке
|
||||
- **AND** её ячейки во всех видимых строках, включая уже отрисованные до закрепления, стоят на месте закреплённой колонки
|
||||
|
||||
#### Scenario: Открепление колонки
|
||||
- **WHEN** пользователь открепляет закреплённую колонку
|
||||
- **THEN** колонка возвращается в прокручиваемую область, а оставшиеся закреплённые колонки сдвигаются и примыкают друг к другу
|
||||
|
||||
#### Scenario: Скрытие и показ закреплённой колонки
|
||||
- **WHEN** пользователь скрывает закреплённую колонку настройками видимости, а потом снова показывает её
|
||||
- **THEN** в обоих состояниях закреплённые колонки примыкают друг к другу, без пустого места на месте скрытой и без наложения после показа
|
||||
|
||||
#### Scenario: Смена ширины закреплённой колонки
|
||||
- **WHEN** пользователь тянет правую границу заголовка закреплённой колонки и отпускает кнопку мыши
|
||||
- **THEN** закреплённые колонки правее неё сдвигаются на новую ширину и примыкают к ней без наложения
|
||||
- **AND** до отпускания кнопки мыши допускается, что соседние закреплённые колонки ещё стоят по старой ширине
|
||||
|
||||
#### Scenario: Смена ширины и видимости незакреплённой колонки
|
||||
- **WHEN** пользователь тянет ширину незакреплённой колонки или скрывает и показывает незакреплённую колонку
|
||||
- **THEN** ширина и набор колонок меняются сразу, заголовок и тело таблицы остаются выровнены по колонкам
|
||||
- **AND** закреплённые колонки не сдвигаются
|
||||
|
||||
### Requirement: Подсветка поиска и фильтров соответствует текущему запросу
|
||||
|
||||
Подсветка совпадений в ячейках таблицы листа SHALL соответствовать текущему значению глобального поиска и фильтров по колонкам. Подсветка — это выделение текста `<mark>` и обводка текущего совпадения. При изменении или сбросе запроса подсветка MUST обновляться во всех видимых ячейках, включая уже отрисованные.
|
||||
|
||||
#### Scenario: Ввод и изменение поискового запроса
|
||||
- **WHEN** пользователь вводит запрос в поле поиска, а затем меняет его
|
||||
- **THEN** после применения запроса во всех видимых ячейках подсвечено совпадение именно с текущим запросом, подсветки прежнего запроса нет
|
||||
|
||||
#### Scenario: Сброс поиска
|
||||
- **WHEN** пользователь очищает поле поиска
|
||||
- **THEN** ни в одной видимой ячейке нет подсветки `<mark>` и обводки текущего совпадения
|
||||
|
||||
#### Scenario: Переход к совпадению
|
||||
- **WHEN** пользователь нажимает Enter или кнопки «следующий/предыдущий результат»
|
||||
- **THEN** таблица прокручивается к совпадению, текущая ячейка обведена, обводка предыдущей снята
|
||||
|
||||
#### Scenario: Фильтр по колонке и его сброс
|
||||
- **WHEN** пользователь задаёт значение фильтра по колонке, меняет его, а затем очищает
|
||||
- **THEN** строки отфильтрованы по текущему значению, подсветка в ячейках этой колонки соответствует текущему значению, после очистки подсветки нет
|
||||
|
||||
### Requirement: Состояние вне данных строки отображается в ячейках как раньше
|
||||
|
||||
Таблица листа SHALL отображать в ячейках состояние, которого нет в данных строки, так же, как до введения мемоизации:
|
||||
- блокировки соавторов;
|
||||
- выделение строки;
|
||||
- ошибки валидации;
|
||||
- разрядность (рубли, тысячи, миллионы);
|
||||
- редактируемость по этапу и роли;
|
||||
- открытый редактор ячейки.
|
||||
|
||||
Изменение этого состояния MUST отражаться в видимых ячейках без прокрутки и без перезагрузки страницы.
|
||||
|
||||
#### Scenario: Блокировка соавтора
|
||||
- **WHEN** второй пользователь в другом браузере открывает ячейку на редактирование, а затем закрывает её
|
||||
- **THEN** у первого пользователя ячейка показывает признак блокировки, пока её редактирует второй, и снимает его после закрытия, в том числе если ячейка была на экране всё это время
|
||||
|
||||
#### Scenario: Выделение строки
|
||||
- **WHEN** пользователь кликает по строке или по номеру строки, а потом кликает ещё раз
|
||||
- **THEN** строка затемняется при выделении и возвращается к обычному виду при снятии выделения
|
||||
|
||||
#### Scenario: Ошибка валидации
|
||||
- **WHEN** пользователь вводит значение, нарушающее правило валидации, а затем исправляет его
|
||||
- **THEN** ячейка подсвечивается как ошибочная и появляется плашка «Контроль ошибок», после исправления подсветка снимается
|
||||
|
||||
#### Scenario: Разрядность
|
||||
- **WHEN** пользователь меняет разрядность в панели настроек
|
||||
- **THEN** все видимые числовые ячейки сразу показывают значения в новой разрядности
|
||||
|
||||
#### Scenario: Редактирование ячейки
|
||||
- **WHEN** пользователь открывает редактор двойным кликом (в колонке ВСП — выпадающий список) и сохраняет значение Enter или кликом вне ячейки
|
||||
- **THEN** редактор открывается в этой ячейке, после сохранения ячейка показывает новое значение, соавтор видит его
|
||||
|
||||
#### Scenario: Добавление и удаление строки
|
||||
- **WHEN** пользователь добавляет строку и удаляет строку
|
||||
- **THEN** таблица показывает актуальный набор строк, значения и признаки ячеек соседних строк не перепутаны
|
||||
|
||||
### Requirement: Контракт для правок таблицы листа: рендер ячейки зависит только от ячейки
|
||||
|
||||
Любая правка таблицы листа SHALL соблюдать контракт мемоизации ячеек. Контракт распространяется на AURORA-1889, 1890, 1891, 1893, 1910 и последующие задачи.
|
||||
|
||||
1. Содержимое и оформление ячейки MUST вычисляться только из объекта ячейки, то есть из данных строки и определения колонки.
|
||||
2. Состояние вне данных строки MUST доходить до ячейки одним из трёх способов:
|
||||
- CSS по атрибутам контейнера, строки или ячейки;
|
||||
- React-контекст;
|
||||
- внешнее хранилище с подпиской (`useSyncExternalStore`).
|
||||
|
||||
Его MUST NOT замыкать в свойствах ячейки из конфигурации таблицы (`muiTableBodyCellProps`, `col.Cell`, `col.Edit`).
|
||||
3. Обработчики событий ячейки, например будущие `onClick`/`onKeyDown` из AURORA-1891, MUST читать изменяемое состояние в момент события через `ref` или `table.getState()`, а не из замыкания.
|
||||
4. Состояние, которое меняет определения колонок, MUST явно пересоздавать набор колонок таблицы и тем сбрасывать мемоизацию. Это редактируемость по этапу и роли, разрядность, правила валидации, раскладка закреплённых колонок, поиск и фильтры.
|
||||
5. Прокрутка MUST NOT вызывать рендер компонента, который владеет экземпляром таблицы (`RealtimeTable`).
|
||||
|
||||
#### Scenario: Новое оформление ячейки по состоянию вне данных
|
||||
- **WHEN** правка добавляет оформление ячейки, зависящее от состояния вне данных строки (например, текущая ячейка навигации или ячейка, к которой перешли из плашки ошибок)
|
||||
- **THEN** оформление задаётся CSS-правилом по атрибутам (`data-row-id`, `data-col-id` и т.п.), контекстом или хранилищем с подпиской
|
||||
- **AND** уже отрисованные ячейки показывают его без прокрутки
|
||||
|
||||
#### Scenario: Обработчик видит актуальное состояние
|
||||
- **WHEN** состояние таблицы (например, редактируемая ячейка) изменилось после последнего рендера ячейки, и пользователь кликает или нажимает клавишу в этой ячейке
|
||||
- **THEN** обработчик принимает решение по текущему состоянию, а не по состоянию на момент рендера ячейки
|
||||
|
||||
#### Scenario: Прокрутка не рендерит владельца таблицы
|
||||
- **WHEN** пользователь прокручивает таблицу листа
|
||||
- **THEN** в профиле React за время прокрутки нет рендеров `RealtimeTable`, есть только рендеры тела таблицы и появившихся ячеек
|
||||
|
||||
### Requirement: Явный сброс мемоизации проверяется автотестом
|
||||
|
||||
Правила явного сброса мемоизации ячеек SHALL быть заданы чистой функцией (ключ сброса) и покрыты node-тестом в стандартной команде `cd web && node --test src/components/RealtimeTable/utils/*.test.js`.
|
||||
|
||||
Ключ MUST меняться:
|
||||
- при закреплении и откреплении колонки;
|
||||
- при скрытии и показе закреплённой колонки;
|
||||
- при смене ширины закреплённой колонки (по отпусканию мыши);
|
||||
- при изменении глобального поиска;
|
||||
- при изменении значения фильтра по колонке.
|
||||
|
||||
Ключ MUST NOT меняться:
|
||||
- при смене ширины незакреплённой колонки;
|
||||
- при скрытии и показе незакреплённой колонки;
|
||||
- при изменении данных;
|
||||
- пока ширину колонки тянут.
|
||||
|
||||
Тест MUST также фиксировать поведение `@tanstack/table-core` 8.20.5: новый массив колонок даёт новые объекты ячеек; смена `columnSizing` при прежнем массиве колонок ячейки не пересоздаёт (поэтому ключ обязателен для ширины закреплённой колонки); смена `columnPinning` пересоздаёт ячейки закреплённых колонок (сторон), центральные ячейки остаются теми же.
|
||||
|
||||
#### Scenario: Ключ реагирует на раскладку закреплённых колонок и фильтры
|
||||
- **WHEN** запускается `node --test` для модуля ключа сброса
|
||||
- **THEN** тесты подтверждают каждое условие «меняется» и «не меняется» из этого требования
|
||||
|
||||
#### Scenario: Допущение о пересоздании ячеек
|
||||
- **WHEN** запускается `node --test` для теста допущения на `@tanstack/table-core`
|
||||
- **THEN** тест подтверждает: при новом массиве `columns` (даже с теми же определениями) объекты `row.getVisibleCells()[i]` новые; при смене `columnSizing` с прежним массивом `columns` — те же; при смене `columnPinning` с прежним массивом `columns` — новые у ячеек закреплённых колонок, у центральных — те же
|
||||
|
||||
### Requirement: Перетаскивание ширины закреплённой колонки не медленнее базы
|
||||
|
||||
Перетаскивание ширины закреплённой колонки на листе FORM_2/AHR SHALL стоить не больше главного потока на шаг движения мыши, чем на ветке AURORA-1837. Медиана `main_thread_ms_per_step` по трём повторам сценария `perf/e2e/scenarios/pinned-resize.spec.ts` на тех же данных и сборке того же типа. Цифры до и после, а также p95 интервала кадра MUST быть приведены в описании PR.
|
||||
|
||||
Порог — цель, а не условие слияния; недобор оформляется follow-up issue (см. требование «Шаг прокрутки…»). Корректная раскладка закреплённых колонок после отпускания мыши — требование корректности.
|
||||
|
||||
#### Scenario: Замер перетаскивания ширины закреплённой колонки
|
||||
- **WHEN** сценарий закрепляет на FORM_2/AHR (роль rf, CPU 1×) две первые колонки данных и тянет правую границу первой из них шагами по 4 px вправо и обратно, с ожиданием кадра после каждого шага
|
||||
- **THEN** медиана `main_thread_ms_per_step` на этой ветке не больше, чем на ветке AURORA-1837
|
||||
- **AND** после отпускания мыши не менее четырёх `td[data-pinned="true"]` строки (`mrt-row-expand`, `sort_order`, две колонки данных) примыкают друг к другу (проверка геометрии в сценарии проходит)
|
||||
Loading…
x
Reference in New Issue
Block a user