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

POST /api/support/kb/articles/{id}/archive — Снять статью базы знаний с учёта

Оператор/супервизор Службы заботы снимает статью с учёта — финальная архивация через soft-delete (DeletedAt) статьи KnowledgeBaseArticleEntity (матрица 4.7 / 7.1–7.2). Отличается от publish-kb-article (toggle статуса Published ↔ Archived): здесь статья выводится из справочника целиком — после операции она исчезает и из публичной выдачи, и из back-office-списка (list-kb-articles показывает только живые статьи). Соответствует терминальному переходу Archived → SoftDelete (DeletedAt) (knowledge-base-article.md). Restrict-семантика: снять с учёта Published-статью нельзя — сначала снятие с публикации (publish=false).

Назначение

Дать Службе заботы штатно вывести неактуальную статью из базы знаний без потери истории (soft-delete, восстановимо администратором/миграцией). Разрешено из Draft/Archived (непубличные статусы); Published блокируется до снятия с публикации.

Metadata

ПолеЗначение
Сервис/контекстsupport
API areasupport (наблюдатель/оператор/супервизор, platform-scope)
Feature groupknowledge-base
Статусdraft
Документdocs/06-services/support/api/support/archive-kb-article.md

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

ПроверкаЗначение
Actor typeUser — Оператор/Супервизор Службы заботы (platform-scope сотрудник ПИН)
Auth policy / Permissionssupport.kb.manage (CRUD/publish/archive статей; permission.md)
Scope (RBAC)platform-scope (ADR-0052) — не агентство/застройщик-tenant
Record-levelне применяется (справочник без owner-а; управляет любой обладатель support.kb.manage)
Tenant isolationTenantId = PIN CompanyId в каждом запросе; статья проверяется на принадлежность tenant
Auditdelete (soft): DeletedAt/DeletedByUserId + аудит + Version; PII нет (контент справочный)

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/support/kb/articles/{id}/archive
Success status200 (снята с учёта; тело — итоговое состояние)
Idempotency headerне применяется (повтор на уже снятой статье → 404, шаг 3)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = PIN CompanyIdTenant isolation
ArticleIdroute {id}GuidДа!= Guid.Empty; существующая не снятая статья (шаг 3)
ActorUserIdidentity claimsGuidДаоператор с support.kb.manage; пишется в DeletedByUserId

Тело запроса пустое. Восстановление снятой статьи в первой версии — вне API (администратор/миграция).

Модель ответа ArchiveKbArticleResult

ПолеТипОбязательностьИсточникMaskingОписание
ArticleIdGuidДаKnowledgeBaseArticleEntity.IdИдентификатор снятой статьи.
SlugstringДа.SlugURL-суффикс/код (/help/{slug}).
PreviousStatusKbStatus (enum строкой)Да.Status до снятияСтатус на момент снятия (Draft/Archived).
DeletedAtDateTimeOffsetДа.DeletedAtМомент снятия с учёта (soft-delete).

Алгоритм

  1. Контекст и доступ. Операцию выполняет оператор/супервизор Службы заботы (ActorUserId из claims) с правом support.kb.manage (иначе 403). support — единый платформенный tenant (TenantId = PIN CompanyId).
  2. Проверка входа. ArticleId не пустой; иначе → VALIDATION_FAILED. Прочих полей нет.
  3. Существование и принадлежность. Статья ищется среди живых (удалённые исключены); не найдена (в т.ч. уже снятая или чужой tenant) → SUPPORT_KB_ARTICLE_NOT_FOUND (404). Принадлежность tenant обязательна (иначе тот же SUPPORT_KB_ARTICLE_NOT_FOUND, platform-scope ADR-0052). Дочерних сущностей нет.
  4. Снятие с учёта.
    • restrict-guard: статья не должна быть Published; PublishedSUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH (409 — сначала снять с публикации через publish-kb-article publish=false);
    • снятие из Draft/Archived (соответствует переходу Archived → SoftDelete; Draft также непубличен — безопасно): статус не меняется, ставится soft-delete;
    • фиксируется DeletedAt = now, DeletedByUserId = ActorUserId, UpdatedAt = now, Version += 1; Status/PublishedAt/Slug/ViewsCount сохраняются (историческая правда).
  5. Согласованность. Soft-delete фиксируется одним согласованным изменением. Интеграционных Kafka-событий нет: база знаний — внутренний справочник (knowledge-base-article.md, README «Events И Consumers»); факт снятия отражают DeletedAt/аудит/Version. Конкурентная гонка редакторов/публикаций → SUPPORT_KB_CONCURRENCY_CONFLICT (409, перечитать).
  6. Результат. Возвращаются ArticleId, Slug, PreviousStatus, DeletedAt → HTTP 200.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone (справочник; Kafka/outbox не задействуются)
External callsnone
Cache / projections / searchстатья исчезает из public FTS/get (search-kb-articles/get-kb-article, фильтр Published + deleted_at is null) и из back-office list-kb-articles; GIN ix_kb_search_vector перестаёт её отдавать после commit (eventual)
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ArticleId = Guid.Empty«Проверьте идентификатор статьи»no retry
FORBIDDEN403нет support.kb.manage«Недостаточно прав»no retry
SUPPORT_KB_ARTICLE_NOT_FOUND404статья не найдена / уже снята (DeletedAt) / чужой tenant«Статья не найдена»no retry
SUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH409статья Published (restrict — сначала снять с публикации)«Сначала снимите статью с публикации»no retry (unpublish → повторить)
SUPPORT_KB_CONCURRENCY_CONFLICT409рассинхрон RowVersion (гонка редакторов/публикаций)«Статья изменена, повторите»retry (перечитать)

Acceptance criteria

  • AC-1 (снятие непубличной статьи). Given статья Draft или Archived в своём tenant, оператор с support.kb.manage; When POST .../kb/articles/{id}/archive; Then 200: DeletedAt = now, DeletedByUserId = actor, Version += 1, Status/PublishedAt/Slug/ViewsCount сохранены (историческая правда); статья исчезает из public-выдачи и back-office-списка. Интеграционных событий нет.
  • AC-2 (Published блокируется). Given статья Published; When снятие с учёта; Then SUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH (409) — сначала снять с публикации (publish=false).
  • AC-3 (повторное снятие). Given статья уже снята (DeletedAt проставлен); When снятие; Then SUPPORT_KB_ARTICLE_NOT_FOUND (404) — среди живых её нет.
  • AC-4 (чужой tenant/нет). Given статья другого tenant или несуществующая; When снятие; Then SUPPORT_KB_ARTICLE_NOT_FOUND (404, без раскрытия, platform-scope).
  • AC-5 (нет прав). Given актор без support.kb.manage; When снятие; Then FORBIDDEN (403).
  • AC-6 (конкуренция). Given параллельная правка/публикация сменила RowVersion; When снятие; Then SUPPORT_KB_CONCURRENCY_CONFLICT (409), перечитать и повторить.

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

  • Операция терминальная и внутренняя; внешних контрактов не меняет. PreviousStatus — enum KbStatus строкой; новые значения enum — additive; удаление/переименование enum или полей ответа — breaking (migration + rollout). Разграничение с publish-kb-article: unpublish меняет статус (Published → Archived), archive — снимает с учёта (DeletedAt).
  • Logs: kb-article archived (ArticleId, Slug, PreviousStatus, actor; PII нет); metrics: support_kb_archived_total{from_status}, latency p95 < 500 мс (SLO README); traces — OTel; dashboard Grafana pin-support; runbook — docs/06-services/support/runbooks/ (будущий путь по конвенции).

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

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

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