# Collaboration (M4)

## Approval workflows

Configured under **Approvals → Agency approval workflow** (default) or **Client → Approvals** (per-client override; "use the agency default" removes the override). Each stage has:

| Field | Meaning |
|---|---|
| type | `internal` (agency) or `client` (client side) |
| min_approvals | Distinct people needed. The same person approving twice is rejected |
| deadline_hours | Optional. Reminder 2 h before, escalation to owners/admins after (`approvals:remind-and-escalate`, every 15 min). Both are sent once |
| approver_user_ids | Optional named approvers. Empty = everyone with the permission |

Rules (`App\Domain\Approvals\ApprovalEngine`):
- A request is bound to one immutable version and its content hash. Any material edit supersedes every open step and restarts review.
- Stages run in order. A **client stage has one step per client on the post**, and each client approves only its own step.
- **Client stages are decided client-side only**: members with `approvals.decide_client` on that client who are *not* internal approvers for it. That means client users, or approvers assigned to the client with the client role "Client User".
- Authors cannot approve their own posts (owners and admins excepted).
- Any "request changes" (comment required) ends the request and notifies the author.
- Delegation (`approvals.delegate`, or an approver of the step) hands a step to another eligible member. It's recorded in `approval_histories` with `delegated_from_user_id`.
- The final decision is audited with actor, timestamp, IP and content hash (`approval_histories` and `tenant_audit_logs`).
- With no workflow configured at all, one internal approval is required.

## Comments

`review_comments` are threaded, can be resolved, and are optionally tied to one destination. Visibility is `internal` (agency only) or `client` (also shown in the portal). Client users only see, and only write, client-visible comments for their own client.

## Notifications

- Events and channels are listed in `config/notifications.php`. In-app notifications are stored in the **tenant** `notifications` table via `TenantDatabaseChannel` (users are master records, so Laravel's default database channel can't be used).
- Delivery runs in a tenant-aware job (`SendWorkspaceNotifications`, queue `notifications`), dispatched after commit. Only active members of the workspace receive anything.
- Members choose channels per event under **Notifications → Preferences**. Admins can add a Slack incoming webhook (`https://hooks.slack.com/…`) for approval requests, escalations and client feedback.

## Client portal

Users with the `client` role land on `/portal` and are redirected away from every agency page (`agency` middleware). Agency users get 404 on portal routes. The portal shows:
- **Overview:** pending approvals, upcoming and recently published posts.
- **Approvals and review:** only this client's destinations, client-visible comments, approve or request changes.
- **Calendar:** read-only.
- **Assets:** only files marked "Share in client portal".
- **Feedback:** messages go to the agency's Approvals inbox, and assigned members plus owners/admins are notified.

Drafts and "changes requested" posts never appear in the portal.

## Calendar

- `/calendar` has month, week and day views, filtered by client and campaign, and shown in any timezone.
- **Drag & drop** calls `CalendarActions::move()`. Scheduled posts are re-scheduled through `SchedulePost`, which re-checks approval, validation and blackouts, cancels the old dispatch and writes a new outbox event. The wall-clock time is kept in the schedule's own timezone, so UTC shifts correctly across DST. Drafts move their `planned_at`.
- **Blackout windows** (agency-wide or per client) block scheduling inside them.
- **Queue slots** are per client (weekday + local time in the client timezone). "Add to queue" takes the next free slot, skipping DST gaps, blackouts and slots already taken by that client.
- **Pause / resume** pulls a scheduled post out of the pipeline (targets → `ready`, schedule `paused`) and later re-schedules it at the same time if that is still in the future.
- **Duplicate / series** creates draft copies, planned daily, weekly or monthly at the same wall-clock time. Each copy still needs review and scheduling. The UI warns about provider duplicate-content rules.

## Campaigns & tasks

- Campaigns belong to one client. Posts can only join campaigns of their own client. Budgets are stored in paise.
- Tasks have a client (or are agency-wide), an assignee (who must have access to that client and is notified), a due date, status and checklist.
