# Internal Messenger CRM

## Overview

The `Messenger interne` module adds real-time internal messaging for CRM users.

Implemented scope:

- direct conversations between 2 users
- group conversations
- broadcast conversations
- global announcement conversations
- text messages only
- unread counters per participant
- real-time updates through Mercure
- global floating quick-access widget
- retention setting and purge command

## Database tables

- `crm_conversation`
- `crm_conversation_participant`
- `crm_conversation_message`

## Conversation types

- `direct`
  - between 2 users
  - an existing direct conversation is reused
- `group`
  - multi-user discussion
  - a new conversation is created
- `broadcast`
  - multi-user discussion with replies disabled for recipients
  - a new conversation is created
- `global`
  - announcement channel for all messenger users
  - a new conversation is created
  - replies are disabled by default

## Website setting

The module reuses the existing `website_setting` mechanism.

Setting key:

- `crm_internal_messenger_retention_days`

Meaning:

- `null`, empty, `0` or `<= 0`: unlimited retention
- positive integer: purge messages older than this number of days

Sync command:

```bash
php bin/console app:sync:website-settings
```

## CRM rights

Rights added:

- `INTERNAL_MESSENGER`
- `INTERNAL_MESSENGER_GROUP`
- `INTERNAL_MESSENGER_BROADCAST`
- `INTERNAL_MESSENGER_GLOBAL`

Sync command:

```bash
php bin/console app:sync:rights
```

## Mercure environment variables

Configure:

```dotenv
MERCURE_URL=
MERCURE_PUBLIC_URL=
MERCURE_JWT_SECRET=
CRM_INTERNAL_MESSENGER_RETENTION_DAYS_DEFAULT=0
```

Retention priority:

1. database value `crm_internal_messenger_retention_days`
2. fallback `CRM_INTERNAL_MESSENGER_RETENTION_DAYS_DEFAULT`
3. unlimited retention

## Mercure topics

Conversation topic:

- `/crm/internal-messenger/conversations/{conversationId}`

User inbox topic:

- `/crm/internal-messenger/users/{userId}/inbox`

User notification topic:

- `/crm/internal-messenger/users/{userId}/notifications`

All updates are published as private Mercure updates.

## Global subscription behavior

The CRM back-office layout now initializes the messenger globally for connected users that have the messenger right.

Implemented behavior:

- Mercure subscriber cookie is added globally on `/admin` responses
- inbox subscription is active outside the full messenger page
- lightweight notification updates are received globally
- the quick widget updates unread badges and recent conversations in real time
- if the currently opened quick conversation matches the incoming event, the thread updates instantly

## Quick messenger widget

The admin layout now includes a floating messenger button in the bottom-right corner.

The widget provides:

- unread badge visible from any CRM page
- recent conversations list
- quick open of a conversation
- reading the latest messages
- quick reply without leaving the current page
- new conversation form with multi-user support
- mute per conversation
- user preferences:
  - sound on/off
  - browser notifications on/off

The full messenger page remains available for extended use.

## Internal endpoints used by the widget

- `GET /admin/internal-messenger/widget/bootstrap`
- `GET /admin/internal-messenger/conversations/{id}/open`
- `POST /admin/internal-messenger/conversations`
- `POST /admin/internal-messenger/conversations/{id}/messages`
- `POST /admin/internal-messenger/conversations/{id}/read`
- `POST /admin/internal-messenger/conversations/{id}/mute`
- `POST /admin/internal-messenger/preferences`

## Local setup

1. configure Mercure variables
2. run migrations:

```bash
php bin/console doctrine:migrations:migrate
```

3. sync website settings:

```bash
php bin/console app:sync:website-settings
```

4. sync rights:

```bash
php bin/console app:sync:rights
```

5. assign messenger rights to the relevant user group

## Real-time test flow

1. open the CRM with 2 users
2. make sure both users can access the internal messenger
3. open the full page or the quick widget
4. send a message from session A
5. verify on session B:
   - unread badge updates immediately
   - toast appears
   - browser notification appears if permission is granted and enabled
   - the conversation moves to the top of the list
   - if the thread is open, the message appears instantly

If Mercure is not configured, the module stays usable without real-time updates.

## Purge command

```bash
php bin/console app:internal-messenger:purge-expired-messages
```

Behavior:

- if retention is disabled: nothing is deleted
- otherwise expired messages are deleted permanently
- conversation `last_message_id` and `last_message_at` are recalculated
- conversations and participants are kept

## Security model

- `ConversationVoter::VIEW`
  - allowed for participants
  - allowed for super admin
- `ConversationVoter::SEND`
  - allowed for participants
  - denied on conversations with replies disabled unless the creator is sending
- messenger routes remain protected by controller right checks
- creation of `group`, `broadcast` and `global` conversations is controlled by dedicated rights

## Current implementation status

Already implemented:

- full page messenger
- global quick widget
- global Mercure subscriptions
- unread counters
- read marking
- multi-user conversation creation
- group/broadcast/global types
- notification payload publication
- retention setting resolution
- purge command

Prepared for future work:

- attachments
- CRM contextual conversations through `context_type` and `context_id`
- advanced search
- richer participant management
- stricter policies for reply-enabled broadcasts if business rules evolve
