Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
180 lines
11 KiB
Markdown
180 lines
11 KiB
Markdown
# PRD — Teams Online Meetings → Interactions, MSP scheduling, recording/transcript capture
|
|
|
|
- Slug: `teams-online-meetings-interactions`
|
|
- Date: `2026-06-01`
|
|
- Status: Draft (design converged; see `../../../../.ai/teams-meetings-interactions-consolidated-plan.md` for the long-form design)
|
|
|
|
## Summary
|
|
|
|
Make Microsoft Teams online meetings first-class **interaction** records in Alga PSA, let MSP users
|
|
**schedule** meetings themselves (not just as a side effect of a client appointment request), and
|
|
**capture the recording and transcript** after a meeting ends — surfaced on the contact/client
|
|
interactions timeline (and appointment views). Meetings are created as **calendar-backed events** so
|
|
their recordings/transcripts are retrievable from Microsoft Graph. Storage: transcript saved as a
|
|
durable internal **document**; recording streamed through an internal authenticated **proxy** (raw Graph
|
|
content URLs are never exposed), with an optional per-tenant blob-download.
|
|
|
|
## Problem
|
|
|
|
Today a Teams meeting only exists as a side effect of an end-client appointment request: the client
|
|
requests, an MSP user approves, and (if enabled) Graph creates a standalone online meeting whose join
|
|
link is stashed on `appointment_requests`. There is **no record on the interactions timeline**, MSP
|
|
users **cannot originate a meeting** themselves, and **nothing captures the recording or transcript**.
|
|
Additionally, the current standalone-onlineMeeting creation path cannot reliably surface
|
|
recordings/transcripts (Graph requires calendar-backed meetings for those APIs).
|
|
|
|
## Goals
|
|
|
|
- Online meetings appear as "Online Meeting" interactions on the contact/client timeline with the join
|
|
link, regardless of edition (CE shows join link only).
|
|
- MSP users can schedule a Teams meeting from the interactions feed and from the schedule-entry popup.
|
|
- After a meeting ends, recording(s) and transcript(s) are captured and surfaced on the interaction and
|
|
appointment detail views, via a manual "Refresh recordings" action (Phase 1) and Graph change
|
|
notifications (Phase 2).
|
|
- Appointment-approval meetings and MSP-initiated meetings share one data model and one interaction
|
|
type.
|
|
|
|
## Non-goals
|
|
|
|
- Importing externally-organized Teams meetings (per-user OAuth browse/import) — deferred.
|
|
- Per-user meeting organizer (everything uses one tenant service account in v1).
|
|
- Co-organizer / presenter role assignment for the creating MSP user.
|
|
- Providers other than Teams (data model is provider-agnostic but only `teams` is implemented).
|
|
- "Recording available" notification emails.
|
|
- Operational gold-plating (metrics, dashboards, retry frameworks) beyond the explicit capability
|
|
diagnostics and the bounded recording-fetch retry cap below.
|
|
|
|
## Users and Primary Flows
|
|
|
|
- **MSP user (internal):**
|
|
- Approves a client appointment request with "Generate Teams meeting" → meeting created, interaction +
|
|
`online_meetings` row created, capture registered.
|
|
- Creates a meeting ad-hoc from the interactions feed (Quick Add → type "Online Meeting" → "Create
|
|
Teams meeting") or from the schedule-entry popup.
|
|
- Views the interaction: Join, list of recording/transcript artifacts (newest first), status, and a
|
|
"Refresh recordings" button.
|
|
- **Client user (client portal):** sees Join, and recording/transcript links only when the tenant has
|
|
enabled client-portal visibility (default MSP-only).
|
|
- **Tenant admin:** configures the meeting organizer service account and the `download_recordings` /
|
|
`expose_recordings_in_portal` toggles in the Teams integration settings page.
|
|
|
|
## UX / UI Notes
|
|
|
|
- New system interaction type **"Online Meeting"**, icon `video` (provider-agnostic; distinct from the
|
|
manual "Meeting" type).
|
|
- `InteractionDetails`: "Online Meeting" section — Join + status ("Recording pending" / "No recording") +
|
|
"Refresh recordings" + an **artifacts list** (each transcript → "View transcript" opening the internal
|
|
document; each recording → "Download recording" hitting the internal proxy).
|
|
- `QuickAddInteraction`: when type = Online Meeting, a "Create Teams meeting" toggle (gated on
|
|
capability) with start/end.
|
|
- `EntryPopup`: "Generate Teams meeting" option when creating a schedule entry.
|
|
- Client-portal appointment views: recording/transcript links next to "Join Teams Meeting", gated on the
|
|
tenant visibility setting.
|
|
- All new interactive elements need stable kebab-case `id`s (UI reflection); all new copy via `t('...')`
|
|
i18n keys.
|
|
|
|
## Requirements
|
|
|
|
### Functional Requirements
|
|
|
|
1. Create meetings as **calendar-backed events** (`POST /users/{organizerUpn}/events`,
|
|
`isOnlineMeeting: true`, `onlineMeetingProvider: 'teamsForBusiness'`); resolve the onlineMeeting id
|
|
from the event `joinUrl`.
|
|
2. Persist a `online_meetings` row + an "Online Meeting" interaction for both creation paths
|
|
(appointment approval and MSP-initiated), linked by `appointment_request_id` or `schedule_entry_id`.
|
|
3. Capture recordings and transcripts as **`online_meeting_artifacts`** rows (collections, not single
|
|
values). Transcript content stored as an internal document (client+contact association,
|
|
`is_client_visible=false` unless opted in). Recording surfaced via internal proxy; optional blob
|
|
download.
|
|
4. Manual "Refresh recordings" action (Phase 1) and Graph change-notification subscriptions (Phase 2)
|
|
both drive the same idempotent fetch handler.
|
|
5. Keep appointment reschedule/cancel in sync: update/delete the calendar event via `provider_event_id`
|
|
for new rows; preserve legacy `online_meeting_id` handling for pre-existing standalone meetings;
|
|
set status `cancelled` on decline.
|
|
6. Tenant settings (Teams integration settings page): organizer service account
|
|
(`default_meeting_organizer_upn` + `default_meeting_organizer_object_id`), `download_recordings`,
|
|
`expose_recordings_in_portal`.
|
|
7. Capability diagnostics surface missing consent / Exchange-side mailbox scoping instead of failing
|
|
silently.
|
|
|
|
### Non-functional Requirements
|
|
|
|
- **Multi-tenant / Citus:** all new tables distributed on `tenant`, PK includes `tenant`,
|
|
application-level integrity (no cross-shard FKs). `online_meeting_artifacts` colocated with
|
|
`online_meetings`.
|
|
- **Transaction discipline:** all Graph create/update/delete calls run **outside** DB transactions; if
|
|
Graph create succeeds but the DB transaction fails, the orphaned calendar event is deleted
|
|
(compensation).
|
|
- **Edition:** `online_meetings`, `online_meeting_artifacts`, and the interaction type are
|
|
edition-agnostic; recording/transcript capture, subscriptions, and renewal jobs are EE-gated.
|
|
- **Bounded retry:** recording fetch capped via `recording_fetch_attempts`, terminal `no_recording`.
|
|
- **Security:** raw Graph content URLs never sent to any client; proxy enforces auth + tenant + (for
|
|
portal) the visibility setting.
|
|
|
|
## Data / API / Integrations
|
|
|
|
- **New core migration** — `online_meetings` (PK `(tenant, meeting_id)`; `provider`,
|
|
`provider_meeting_id`, `provider_event_id`, `organizer_upn`, `organizer_user_id`, `subject`,
|
|
`join_url`, `start_time`, `end_time`, `status` ∈
|
|
{scheduled, ended, recording_pending, recording_ready, no_recording, cancelled, failed},
|
|
`recording_fetch_attempts`, `last_fetch_at`, `appointment_request_id`, `interaction_id`,
|
|
`schedule_entry_id`, `created_by`, timestamps) **and** `online_meeting_artifacts`
|
|
(PK `(tenant, artifact_id)`; `meeting_id`, `artifact_type` ∈ {recording, transcript},
|
|
`provider_artifact_id`, `content_url`, `document_id`, `file_id`, `created_date_time`, timestamps;
|
|
unique `(tenant, meeting_id, artifact_type, provider_artifact_id)`).
|
|
- **New core migration** — insert "Online Meeting" into `system_interaction_types` (icon `video`).
|
|
- **EE migration** — add `default_meeting_organizer_object_id`, `download_recordings`,
|
|
`expose_recordings_in_portal` (+ Phase 2 subscription id/expiry) to `teams_integrations`.
|
|
- **Types** — `IOnlineMeeting` + `IOnlineMeetingArtifact` (with `artifacts[]`); optional
|
|
`online_meeting` on `IInteraction`.
|
|
- **Graph (EE)** — change `createTeamsMeeting`/`update`/`delete` to the events endpoints; new
|
|
`fetchMeetingArtifacts` (recordings + transcripts collections, transcript content); reuse
|
|
`fetchMicrosoftGraphAppToken` / `resolveTeamsMeetingExecutionConfig`. URL-encode id path segments.
|
|
- **Permissions (document, admin-consent)** — `Calendars.ReadWrite` (application) **scoped via an
|
|
Exchange Application Access Policy / RBAC to the organizer mailbox**, plus protected/metered
|
|
`OnlineMeetingRecording.Read.All`, `OnlineMeetingTranscript.Read.All` on top of existing
|
|
`OnlineMeetings.ReadWrite.All` + Teams Application Access Policy.
|
|
|
|
## Security / Permissions
|
|
|
|
- `scheduleTeamsMeeting` requires an explicit `hasPermission` check (confirm resource/action in repo
|
|
catalog) in addition to `withAuth`.
|
|
- Recording proxy route: authenticated, tenant-scoped, portal-visibility-gated.
|
|
- Transcript documents default `is_client_visible=false`.
|
|
- `Calendars.ReadWrite` must be mailbox-scoped on the Exchange side (Teams Application Access Policy does
|
|
not scope calendar/mailbox access).
|
|
|
|
## Observability
|
|
|
|
Out of scope beyond capability diagnostics (consent/scoping warnings) — no metrics/dashboards.
|
|
|
|
## Rollout / Migration
|
|
|
|
- New tables/type ship in CE & EE; capture features are EE-only and no-op in CE.
|
|
- **No backfill** of appointments approved before launch (they keep `online_meeting_*` columns +
|
|
email/portal links; no timeline interaction).
|
|
- Phase 1 (manual refresh) ships first; Phase 2 (subscriptions + renewal job) follows.
|
|
- New Graph permissions + Exchange scoping documented in Teams setup docs; not auto-granted.
|
|
|
|
## Open Questions
|
|
|
|
- Exact `hasPermission` resource/action for `scheduleTeamsMeeting` (confirm against repo catalog).
|
|
- Whether MSP-initiated meetings should default to including the contact as an attendee or leave it
|
|
optional in the UI.
|
|
|
|
## Acceptance Criteria (Definition of Done)
|
|
|
|
- Approving an appointment with a Teams meeting creates an "Online Meeting" interaction + `online_meetings`
|
|
row (calendar-backed), with no external attendees, and the join link still appears in email/portal.
|
|
- An MSP user can create a meeting from the interactions feed and the schedule popup; both produce the
|
|
same interaction + row; a schedule entry (when created) has `work_item_id = interaction_id`.
|
|
- After recording a meeting, "Refresh recordings" populates transcript document(s) + recording
|
|
artifact(s); the interaction lists all artifacts; recording plays via the proxy; no raw Graph URL is
|
|
exposed.
|
|
- Reschedule updates, and cancel/decline cancels, the calendar event via `provider_event_id` (legacy
|
|
rows still handled); all Graph calls run outside DB transactions; orphaned events are cleaned up on DB
|
|
failure.
|
|
- Client portal shows recording/transcript only when `expose_recordings_in_portal` is on.
|
|
- CE build shows the interaction with join link only and no recording UI errors.
|
|
- Migrations apply/rollback cleanly on plain Postgres and Citus.
|