> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yume.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Чтение, архивация и список

> Личный кабинет уведомлений сотрудника: постраничный список с поиском и фильтрами, счётчик непрочитанных, пометка «прочитано» с захватом одноразовых по принципу «первый выиграл» и архивация — всё поверх персональных строк состояния.

Полный перечень полей — в [глоссарии](/ru/logic/notifications/read-archive-glossary).

## Что делает эта часть модуля

Этот раздел отвечает за «личный кабинет уведомлений» сотрудника: постраничный список полученных уведомлений с поиском и фильтрами, счётчик непрочитанных, а также действия «пометить прочитанным» и «отправить в архив». Всё это работает поверх двух сущностей: самого **Уведомления** (общего для компании события) и **Уведомления пользователя** — персональной строки состояния, которая заводится отдельно для каждого получателя и хранит признак прочтения/архивации.

Ключевая идея: у одного уведомления нет глобального статуса «прочитано». Вместо этого прочтение и архивация — это *персональные* факты. Пока у сотрудника нет строки состояния по данному уведомлению, оно для него непрочитано. Как только строка появляется — оно прочитано. Если в строке стоит признак архива — оно в архиве.

## Модель данных, задействованная здесь

| Поле               | Тип        | Назначение                                                                                |
| ------------------ | ---------- | ----------------------------------------------------------------------------------------- |
| Компания           | связь      | Владелец уведомления; вся выборка всегда ограничена текущей компанией                     |
| Текст              | текст      | Содержимое сообщения; именно по нему работает поиск в списке                              |
| Автор/источник     | связь      | Пользователь-инициатор события; в выдаче отдаётся числом-идентификатором                  |
| Тип объекта        | текст      | Код связанной сущности (заявка, задача по лиду, документ и т.д.); используется как фильтр |
| ID объекта         | число      | Идентификатор связанной сущности                                                          |
| Обновлено          | дата/время | По убыванию этого поля строится и сортировка, и курсорная постраничность                  |
| Одноразовый захват | флаг       | Уведомление, которое «забирает себе» только один сотрудник                                |
| Кем захвачено      | связь      | Пользователь, забравший одноразовое уведомление                                           |
| Время захвата      | дата/время | Момент захвата                                                                            |

А персональное состояние получателя описывается так:

| Поле        | Тип        | Назначение                                                                         |
| ----------- | ---------- | ---------------------------------------------------------------------------------- |
| Компания    | связь      | Владелец записи                                                                    |
| Уведомление | связь      | Родительское уведомление                                                           |
| Получатель  | связь      | Сотрудник, для которого хранится состояние                                         |
| В архиве    | флаг       | Признак архивации у этого сотрудника                                               |
| Создано     | дата/время | Момент появления строки; само наличие строки и означает, что уведомление прочитано |

<Info>
  Наличие строки **Уведомления пользователя** означает «прочитано». Признак «В архиве» внутри этой строки — отдельное, более сильное состояние: архив всегда подразумевает, что строка уже существует, то есть уведомление уже считается прочитанным.
</Info>

## Базовый запрос: что вообще видит сотрудник

Все три сценария (список, счётчик, пометка прочтения) стартуют с одной и той же базовой выборки:

1. Берутся только уведомления **текущей компании**.
2. Из выборки **исключаются** одноразовые уведомления, которые уже забрал кто-то другой. Точное правило исключения: уведомление помечено как одноразовое, поле «кем захвачено» заполнено, и захватил его *не* текущий сотрудник.
3. Сортировка — по убыванию поля «Обновлено» (самые свежие сверху).

Из правила 2 следует важное поведение: одноразовое уведомление видно всем адресатам ровно до тех пор, пока его никто не забрал. После захвата оно мгновенно исчезает из списков всех остальных, но остаётся видимым тому, кто его забрал.

<Note>
  Обычные (не одноразовые) уведомления это правило не трогает — они остаются видимыми всем адресатам независимо от того, кто их уже прочитал.
</Note>

## Список уведомлений

Список отдаётся постранично с **курсорной пагинацией** (курсор строится по полю «Обновлено», по убыванию). Курсорная схема устойчива к вставке новых записей во время листания — в отличие от нумерации страниц, она не «съезжает», когда сверху появляются свежие уведомления.

Поверх базового запроса работают:

* **Поиск** — по тексту уведомления. Ищется вхождение строки в содержимое сообщения.
* **Фильтры** (см. ниже) — по прочтению, архиву и типу связанного объекта.

Каждое уведомление в выдаче содержит идентификатор, текст, автора/источник (отдаётся числом-идентификатором), тип и ID связанного объекта, время создания, дополнительные данные, а также признак одноразовости и информацию о захвате (кем и когда забрано). Поля времени создания, признака одноразовости и данных о захвате доступны только для чтения.

## Фильтры списка

Фильтр вычисляет состояние не по самому уведомлению, а по наличию у сотрудника соответствующей строки состояния.

| Фильтр      | Значение                 | Что показывает                                                               |
| ----------- | ------------------------ | ---------------------------------------------------------------------------- |
| Тип объекта | один или несколько кодов | Только уведомления с указанным типом связанной сущности                      |
| В архиве    | да                       | Только те, по которым у сотрудника есть строка состояния с признаком архива  |
| В архиве    | нет                      | Всё, кроме заархивированных сотрудником                                      |
| Прочитано   | да                       | Только те, по которым у сотрудника есть строка состояния без признака архива |
| Прочитано   | нет                      | Всё, кроме прочитанных (но не заархивированных)                              |

<Warning>
  Фильтры «Прочитано» и «В архиве» не взаимоисключающи и опираются на разные условия. «Прочитано = да» требует строку состояния именно **без** признака архива, а «В архиве = да» — строку **с** признаком архива. Поэтому уведомление, отправленное в архив, под фильтр «Прочитано = да» не попадёт (его строка помечена как архивная), хотя по смыслу оно тоже прочитано. Чтобы получить «активные прочитанные», используйте «Прочитано = да»; чтобы «непрочитанные и не в архиве» — «Прочитано = нет» вместе с «В архиве = нет».
</Warning>

## Счётчик непрочитанных

Счётчик считается как разность двух чисел:

* количество всех видимых сотруднику уведомлений (та же базовая выборка компании с исключением чужих захваченных одноразовых);
* минус количество строк состояния этого сотрудника (то есть всё, что он уже так или иначе тронул — прочитал или заархивировал).

Иначе говоря: **непрочитанные = всё видимое − всё, по чему уже есть персональная строка состояния**.

<Warning>
  Счётчик считает *все* строки состояния сотрудника, включая архивные. Поэтому заархивированное уведомление тоже уменьшает счётчик непрочитанных — архивация неявно снимает уведомление со счётчика, даже если сотрудник не нажимал «прочитать».
</Warning>

<Note>
  Разность вычисляется по количествам, а не по совпадению конкретных записей. В нормальном потоке строки состояния всегда относятся к видимым уведомлениям, поэтому число корректно. Но это именно арифметика двух счётчиков, а не точное множество непрочитанных.
</Note>

## Пометка «прочитано»

Действие принимает либо список идентификаторов уведомлений, либо флаг «прочитать все». Вся операция выполняется в одной транзакции.

Порядок работы:

1. Формируется список уведомлений компании, по которым у сотрудника **ещё нет** строки состояния (уже прочитанные пропускаются). Если флаг «прочитать все» не выставлен — список дополнительно сужается до переданных идентификаторов.
2. Из этого списка исключаются одноразовые уведомления, уже захваченные другим сотрудником (то же правило, что и в базовом запросе).
3. Для каждого оставшегося уведомления:
   * Если это **обычное** уведомление — просто заводится строка состояния «прочитано».
   * Если это **одноразовое** и ещё никем не захваченное — выполняется захват «первый выиграл» (см. ниже).
4. Все новые строки состояния создаются одной пакетной вставкой.

### Захват одноразового уведомления «первый выиграл»

Для одноразового уведомления система не полагается на прочитанное ранее значение. Вместо этого выполняется **условное обновление**: «проставить, кто и когда забрал» — но только при условии, что поле «кем захвачено» всё ещё пустое.

* Если обновление затронуло запись — значит, текущий сотрудник выиграл гонку. Ему заводится строка состояния, а всем остальным по вебу отправляется команда убрать это уведомление (отзыв из веб-ленты).
* Если обновление не затронуло ни одной записи — значит, кто-то опередил долю секунды. Тогда для текущего сотрудника это уведомление **тихо пропускается**: строка состояния не создаётся, ошибка не возвращается.

<Tip>
  Условие «поле захвата ещё пустое» прямо в запросе обновления — это и есть защита от гонки. Двум одновременным запросам не удастся оба «выиграть»: базой данных проходит ровно один, второй получит ноль изменённых строк и корректно отвалится.
</Tip>

<Warning>
  При «прочитать все» проигравшие гонку за одноразовое уведомление просто не попадут в результат по нему — но остальные уведомления в том же запросе будут отмечены прочитанными. Частичный успех здесь — норма, а не ошибка.
</Warning>

## Архивация

Действие принимает либо список идентификаторов, либо флаг «архивировать все». Тоже выполняется в одной транзакции.

* **Без флага «архивировать все»**: среди строк состояния сотрудника по переданным уведомлениям берутся те, что ещё не в архиве, и им проставляется признак архива. Важное следствие: архивируются только те уведомления, по которым у сотрудника **уже есть** строка состояния (то есть уже прочитанные). Если строки нет — архивировать нечего, уведомление останется как есть.
* **С флагом «архивировать все»**: сначала для всех уведомлений компании, по которым у сотрудника ещё нет строки состояния, эти строки досоздаются пакетно, а затем все его неархивные строки переводятся в архив. Так «архивировать всё» захватывает даже те уведомления, которые сотрудник до этого не открывал.

<Note>
  «Архивировать все» действует как «прочитать всё и убрать в архив» одновременно: оно доводит непрочитанные уведомления до состояния прочитанных, создавая строки состояния, и только потом архивирует их.
</Note>

<Warning>
  В отличие от пометки прочтения, архивация **не выполняет** захват одноразовых уведомлений и не рассылает отзыв из веб-ленты. При «архивировать все» строки состояния досоздаются напрямую для всех уведомлений компании — механизм «первый выиграл» здесь не применяется. Захват одноразовых происходит только через действие «прочитать».
</Warning>

## Как это используется

Типовой сценарий фронтенда:

1. Показать бейдж с числом непрочитанных — берётся из счётчика.
2. Открыть ленту — постраничный список, при необходимости с поиском по тексту и фильтрами по прочтению/архиву/типу объекта.
3. При просмотре — отметить одно или несколько уведомлений прочитанными (или все сразу).
4. Убрать лишнее — отправить в архив по списку или целиком.

Все действия требуют аутентификации и всегда работают в контексте текущей компании: сотрудник видит и меняет только уведомления своей компании и только своё персональное состояние по ним.

## Особенности поведения

* Архивация по списку (без «архивировать все») затрагивает только те уведомления, по которым у сотрудника уже есть строка состояния; для непрочитанных уведомлений без строки состояния операция ничего не делает.
* «Архивировать все» неявно помечает прочитанными все непрочитанные уведомления компании, досоздавая строки состояния напрямую; при этом механизм захвата одноразовых не используется и отзыв из веб-ленты не рассылается.
* Отзыв одноразового уведомления из веб-ленты у остальных сотрудников происходит только через действие «прочитать» (после успешного захвата), но не при архивации.
* Список использует курсорную пагинацию и сортировку по убыванию поля «Обновлено», а поиск работает только по тексту уведомления.
* Базовый запрос скрывает одноразовые уведомления, уже захваченные другим сотрудником, но оставляет их видимыми тому, кто захватил; обычные уведомления остаются видимыми всем адресатам независимо от прочтения другими.

## Связанные страницы

<CardGroup cols={2}>
  <Card title="Глоссарий: Чтение, архивация и список" icon="book" href="/ru/logic/notifications/read-archive-glossary">
    Полный перечень полей уведомления и персональной строки состояния, фильтров и параметров действий.
  </Card>

  <Card title="Уведомления (доставка и доступ)" icon="bell" href="/ru/logic/notifications">
    Обзор всего модуля уведомлений: доставка по каналам, доступы и настройки.
  </Card>

  <Card title="Модель и веер доставки" icon="sitemap" href="/ru/logic/notifications/model-dispatch">
    Модели уведомления и персонального состояния, сигнал сохранения и разветвление доставки по каналам с учётом доступов.
  </Card>

  <Card title="Создание и группировка" icon="layer-group" href="/ru/logic/notifications/creation-grouping">
    Фоновые задачи создания уведомлений: одиночные, обновление по объекту и группировка в 30-минутном окне с накоплением.
  </Card>

  <Card title="Каналы доставки" icon="tower-broadcast" href="/ru/logic/notifications/delivery-channels">
    Транспортные задачи и сервис: веб-доставка, push на устройства с деактивацией мёртвых токенов и рассылка в Telegram.
  </Card>

  <Card title="Push-токены устройств" icon="mobile-screen" href="/ru/logic/notifications/push-tokens">
    Регистрация и отписка push-токенов мобильных устройств сотрудника с уникальностью по устройству и типом платформы.
  </Card>

  <Card title="Привязка Telegram" icon="paper-plane" href="/ru/logic/notifications/telegram-link">
    Привязка и отвязка аккаунта сотрудника к Telegram-боту по одноразовому коду верификации.
  </Card>

  <Card title="Доступ и настройки" icon="sliders" href="/ru/logic/notifications/access-settings">
    Матрица доступов «источник × тема» на пользователя, перечни источников и тем и массовое редактирование настроек.
  </Card>
</CardGroup>
