Перейти к основному содержимому

GET /api/v1/profile/requisite-templates — Список шаблонов реквизитов (listRequisiteTemplates)

Назначение

Вернуть активные (и опционально архивные по флагу includeArchived) шаблоны реквизитов текущего инвестора — для выбора в форме брони/сделки CRM. Список без PII: Composition хранит только id документов (открыто), контакты-оверрайды не расшифровываются — отдаётся лишь признак наличия (hasContactOverride). Расшифрованный снапшот выдаётся отдельно, только по m2m-запросу CRM (internal API снапшота, requisite-template.md). Сортировка «недавние»: LastUsedAt desc, затем CreatedAt desc. Вызывает инвестор из формы брони/сделки.

Metadata

ПолеЗначение
Сервис/контекстprofile
API areapublic
Feature grouprequisites
Статусdraft
Документdocs/06-services/profile/api/public/list-requisite-templates.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (Bearer JWT), UserType = Investor (INV-PRF-4)
Auth policy / Permissionsauthenticated; permission profile.requisites.manage-self (отдельного view нет — управление своими реквизитами)
Scope (RBAC)self
Record-levelRequisiteTemplateEntity.OwnerUserId = UserId из sub
Tenant isolationне применяется (ADR-0052)
Auditread: список без PII (id/имена полей); значения контактов не расшифровываются и не аудируются

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/profile/requisite-templates
Success status200
Idempotency headerне применяется
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный JWT401 без токена
includeArchivedqueryboolНет (default false)true/false
PagequeryintНет (default 1)>= 1
SizequeryintНет (default 20)1..20 (агрегаты малы, ≤ 20 активных, INV-RQT-7); иначе ValidationError

Модель ответа — постраничный список RequisiteTemplateModel

Ответ — страница элементов (Items) и метаданные страницы (Meta). Дом элемента — requisite-template.md.

ПолеТипОбязательностьИсточникMaskingОписание
ItemsIReadOnlyList<RequisiteTemplateModel>Да (может быть пустым)projection RequisiteTemplateEntityСтраница шаблонов; порядок — LastUsedAt desc (NULLS LAST), затем CreatedAt desc.
MetaPagingMetaДаTotal, Take = Size, Offset, HasMoreМетаданные страницы (Total — всего под фильтром, для UI-лимита 20).

Модель RequisiteTemplateModel

Проекция агрегата без расшифровки PII (дом полей — requisite-template.md).

ПолеТипОбязательностьИсточникMaskingОписание
idGuidДаRequisiteTemplateEntity.Id
namestringДа.NameИмя шаблона (не PII: «Реквизиты жены», «На дочь»).
personIdGuidДа.PersonIdПерсона документов сделки.
statusRequisiteTemplateStatusДа.StatusActive/Archived.
isDefaultboolДа.IsDefaultШаблон по умолчанию для формы брони (INV-RQT-5).
compositionRequisiteTemplateCompositionModelДа.Composition (jsonb)Только id документов — без PII (модель ниже).
hasContactOverrideboolДаcomputed: ContactEmailCipher != null || ContactPhoneCipher != nullзначения не отдаютсяПризнак контактов-оверрайдов; сами email/phone не расшифровываются и наружу не идут.
lastUsedAtDateTimeOffset?Нет.LastUsedAtПоследнее использование (сортировка «недавние»).
usageCountintДа.UsageCountСколько раз запрошен снапшот брони/сделки.
versionlongДа.VersionДля optimistic-обновлений.

Модель RequisiteTemplateCompositionModel

Проекция jsonb RequisiteTemplateComposition (дом — requisite-template.md); только id, plaintext.

ПолеТипОбязательностьИсточникОписание
identityDocumentIdGuidДаComposition.IdentityDocumentIdid документа-удостоверения (паспортный тип, INV-RQT-2).
innDocumentIdGuid?НетComposition.InnDocumentIdid документа ИНН, если задан.
additionalDocumentIdsList<Guid>Да (может быть пустым)Composition.AdditionalDocumentIds≤ 10 id дополнительных документов персоны.

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор (UserId из токена); tenant не применяется (ADR-0052); требуется право profile.requisites.manage-self.
  2. Проверка входа. UserId присутствует; includeArchived — bool; Page >= 1, Size ∈ [1..20] — иначе ValidationError.
  3. Проверка предусловий и выборка. Профиль владельца существует; иначе PROFILE_NOT_FOUND. Профиль в режиме самоуправления (ContactsManagedBy = SelfService, иначе PROFILE_MANAGED_EXTERNALLY, INV-PRF-2), активен (Status = Active, иначе PROFILE_USER_BLOCKED, INV-PRF-3), тип пользователя — Investor (иначе PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE, INV-PRF-4). Отобрать страницу шаблонов владельца (активные, а при includeArchived = true — и архивные), сортировка LastUsedAt desc (NULLS LAST), затем CreatedAt desc; постранично по Page/Size. Персона и документы не включаются (composition — id, признак контактов — по наличию шифрованных значений).
  4. Маппинг без расшифровки PII. Спроецировать RequisiteTemplateEntity → RequisiteTemplateModel поле-в-поле; Composition десериализуется из jsonb в RequisiteTemplateCompositionModel (только id; при ContractVersion < актуальной — lazy-миграция в памяти, additive-правило дома); hasContactOverride = ContactEmailCipher != null || ContactPhoneCipher != null (по наличию значений, без декодирования). Расшифровка не выполняется — список без PII.
  5. Результат. Страница Items + Meta { Total, Take = Size, Offset, HasMore }; HTTP 200. Freshness: source of truth, без кэша; record-level гарантирован фильтром OwnerUserId; пустой набор — 200 с пустым Items.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/просрочен JWTбез телаre-login
ValidationError400Пустой UserId / неверный pagingперечень невалидных полейno retry
PROFILE_NOT_FOUND404Профиль не создан консюмером identity«Профиль ещё создаётся»retry с backoff
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE403UserType != Investor (INV-PRF-4)«Раздел доступен только инвесторам»no retry
PROFILE_MANAGED_EXTERNALLY403ContactsManagedBy = TalentSync (INV-PRF-2)«Данные управляются организацией»no retry
PROFILE_USER_BLOCKED403Status != Active (INV-PRF-3)«Профиль заблокирован»no retry

Совместимость и наблюдаемость

  • Новые optional поля RequisiteTemplateModel — additive; RequisiteTemplateCompositionModel растёт по ContractVersion дома (lazy-миграция); unknown status у клиента — скрыть действия. Удаление/ переименование/смена типа — breaking (migration + rollout).
  • Logs: UserId, TotalCount, TemplateId/PersonId (без значений контактов и без PII — правило requisite-template.md); metrics: profile_api_operations_total{operation="listRequisiteTemplates",result}; traces — OTel; dashboard profile-overview.

Acceptance Criteria

  • Given инвестор с 3 активными шаблонами, When GET page=1&size=20, Then 200; Items отсортированы LastUsedAt desc (NULLS LAST) затем CreatedAt desc; composition — только id; hasContactOverride вычислен по наличию шифрованных значений; контакты не расшифрованы; Meta.Total отражает всё под фильтром.
  • Given includeArchived = true, When GET, Then в выборку включены и архивные шаблоны.
  • Given Size вне [1..20] или Page < 1, When GET, Then ValidationError (400).
  • Given у инвестора нет шаблонов, When GET, Then 200 с пустым Items.
  • Given ContractVersion состава < актуальной, When GET, Then lazy-миграция Composition в памяти (additive), без ошибки.
  • Given TalentSync / заблокирован / UserType != Investor, When GET, Then PROFILE_MANAGED_EXTERNALLY (403) / PROFILE_USER_BLOCKED (403) / PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE (403) соответственно.
  • Given профиль ещё не создан консюмером identity, When GET, Then PROFILE_NOT_FOUND (404), retry с backoff.
  • Given любой успешный ответ, Then значения контактов-оверрайдов наружу не отдаются и не логируются; record-level гарантирован фильтром OwnerUserId.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы