v1.12 — Chat with context-bound discussions + CalDAV
v1.12 — Chat that knows context
Чат в HRMS теперь живёт двумя слоями:
- Standalone direct messages на `/conversations` (как Telegram-lite)
- Context-bound discussions прямо на странице сущности (Leave Request / Job Applicant) — обсуждение прикреплено к конкретному артефакту, без переключения вкладок
💬 Standalone chat
- `/conversations` — список с unread-badge в sidebar nav
- `/conversations/new` — multi-recipient selector
- Composer: Enter=send, Shift+Enter=newline
- Live broadcast через Turbo Streams — новое сообщение прилетает в открытые вкладки участников без F5
- Read-tracking (last_read_at) → unread-каунтер обнуляется при открытии
🎯 Context-bound discussions (главная фича)
- Открой `/leave_requests/123` → внизу страницы дискуссия с HR + manager + employee автоматически
- `/job_applicants/456` → recruiter + HR могут обсуждать кандидата inline
- `Conversation.discussion_for(target, viewer)` — idempotent: 1 тред на 1 сущность, новые участники подтягиваются автоматически
- В общем `/conversations` context-bound треды получают tag `🌴 Leave #123` со ссылкой на сущность
- Pivot из «ещё один Slack»: discussion привязан к артефакту → видно зачем вообще писать сообщение
📅 CalDAV subscribe feed
`GET /api/v1/me/calendar.ics?token=` — подписываемый feed для Google Calendar / Outlook / Apple Calendar:
- Интервью пользователя (interviewer = current_user, ±диапазон 30d/180d)
- Дни рождения сотрудников (для HR/manager/superadmin, ≤90d вперёд)
- Без cancelled/no_show
- Bearer auth через `?token=...` query-param (Google Calendar не умеет Authorization header — поэтому fallback)
Migrations
- `conversations` (with polymorphic `targetable`, unique partial index)
- `conversation_participants` (composite unique)
- `messages` (conversation_id + created_at)
i18n
- +22 keys × 3 locales → 2355/2355/2355
Quality
- 120 examples, 0 failures (1 pre-existing pending)
- Rubocop: 0 offenses
- Zeitwerk: clean