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
32 KiB
32 KiB
Scratchpad — Teams Meeting Link on Appointment Approval
Key file paths
Scheduling (CE-safe)
- Approval action:
packages/scheduling/src/actions/appointmentRequestManagementActions.ts(approveAppointmentRequest~L386-824; email dispatch ~L703-814; schedule-entry creation ~L470-639) - Approval schema:
packages/scheduling/src/schemas/appointmentRequestSchemas.ts(approveAppointmentRequestSchema~L34-41) - Approval panel UI:
packages/scheduling/src/components/schedule/AppointmentRequestsPanel.tsx - Entry popup UI:
packages/scheduling/src/components/schedule/EntryPopup.tsx - Availability Settings UI:
packages/scheduling/src/components/schedule/AvailabilitySettings.tsx(tabs at L778-784, total ~1371 lines) - Availability Settings action:
packages/scheduling/src/actions/availabilitySettingsActions.ts - ICS generator:
packages/scheduling/src/utils/icsGenerator.ts(interfaceICSEventDataL6-18; already supportsurlL101-102 andlocationL97-99) - CE shim:
packages/scheduling/src/lib/teamsMeetingService.ts
Client portal
- Detail page:
packages/client-portal/src/components/appointments/AppointmentRequestDetailsPage.tsx - Cancel action:
packages/client-portal/src/actions/client-portal-actions/appointmentRequestActions.ts
EE — Microsoft Teams
- Profile + secret resolver:
ee/packages/microsoft-teams/src/lib/auth/teamsMicrosoftProviderResolution.ts(resolveTeamsMicrosoftProviderConfigImplL30-79) - Shared Graph auth utility:
ee/packages/microsoft-teams/src/lib/graphAuth.ts - Integration state:
ee/packages/microsoft-teams/src/lib/actions/integrations/teamsActions.ts(getTeamsIntegrationStatusImpl,getTeamsIntegrationExecutionStateImpl) - Meeting helpers:
ee/packages/microsoft-teams/src/lib/meetings/createTeamsMeeting.ts,ee/packages/microsoft-teams/src/lib/meetings/meetingConfig.ts - New actions target dir:
ee/packages/microsoft-teams/src/lib/actions/meetings/ - Capability action:
ee/packages/microsoft-teams/src/lib/actions/meetings/meetingCapabilityActions.ts
Migrations
- Appointment requests:
server/migrations/20251110223310_create_appointment_requests.cjs(base),server/migrations/20260416210000_add_requester_timezone_to_appointment_requests.cjs - Added meeting columns migration:
server/migrations/20260423130000_add_online_meeting_columns_to_appointment_requests.cjs - Teams integrations:
ee/server/migrations/20260307153000_create_teams_integrations.cjs - Added organizer migration:
ee/server/migrations/20260423131000_add_default_meeting_organizer_to_teams_integrations.cjs - Email templates:
server/migrations/20251111123313_add_appointment_request_email_templates.cjs+server/migrations/20251209175019_update_appointment_email_templates_modern_styling.cjs(both seedsystem_email_templates)
- Service:
@alga-psa/emailpackage —SystemEmailService.sendAppointmentRequestApproved(data, options)and.sendAppointmentAssignedNotification - Current approved template fields:
serviceName, appointmentDate, appointmentTime, duration, technicianName, technicianEmail, technicianPhone, minimumNoticeHours, contactEmail, contactPhone, calendarLink - Template language: Handlebars via
replaceVariables() - Will add:
onlineMeetingUrl(and probablyonlineMeetingProviderfor future-proofing)
i18n
- Schedule namespace:
server/public/locales/<lang>/msp/schedule.json(human locales: en, de, es, fr, it, nl, pl, pt; pseudo: xx, yy) - Pseudo generator:
scripts/generate-pseudo-locales.cjs - Validator:
scripts/validate-translations.cjs
Decisions
- Organizer model: option (a) — one designated tenant-level UPN stored on
teams_integrations. All meetings created as that user. Requires Azure admin to set up an Application Access Policy grantingOnlineMeetings.ReadWrite.Allfor this specific user. - Admin UI home: Availability Settings → Teams Meetings tab (not a dedicated integrations page). Rationale: tab only appears after Teams is active; it's tightly coupled to the appointment-approval flow, which lives in Scheduling. Avoids standing up a new integrations-hub surface.
- Toggle default: ON when capability is available. Approver can uncheck per-appointment (e.g., on-site visits).
- Reschedule: PATCH via Graph. Only times updated; subject left alone.
- Cancel/delete: DELETE via Graph + confirmation dialog warning the user the Teams meeting will go away.
- Client portal: show Join button. Clients don't need SSO to join — anonymous join still works.
- Failure UX: warning toast. Action return shape gains
teamsMeetingWarning?: string. - Out of scope MVP: retries, orphaned-meeting janitor, per-user delegated OAuth, per-service auto-toggle, recording settings.
Azure setup (for admin runbook)
Tenant admin must do once in Azure:
- Grant the Microsoft profile's app registration the application permission
OnlineMeetings.ReadWrite.All(admin consent required). - Create an Application Access Policy and assign the designated organizer user to it:
New-CsApplicationAccessPolicy -Identity Alga-Meetings -AppIds "<clientId>" -Description "Alga PSA appointment meetings" Grant-CsApplicationAccessPolicy -PolicyName Alga-Meetings -Identity "scheduling@acme.com" - Wait ~5-10 min for policy propagation.
- Enter the organizer UPN in Availability Settings → Teams Meetings tab and click Verify.
Open questions / verification needed before implementation
- Verify CE shim pattern already used elsewhere in the codebase for EE features — look at how calendar or activity notifications are consumed from CE-safe packages today. Mirror that pattern.
- Decide whether
APPOINTMENT_ONLINE_MEETING_ATTACHEDis worth a new event type or ifSCHEDULE_ENTRY_UPDATEDis sufficient.
Commands
# CE build
npm run build
# EE build
npm run build:ee
# Run migrations
npm run migrate
# Regenerate pseudo-locales after adding new English keys
node scripts/generate-pseudo-locales.cjs
# Validate translations
node scripts/validate-translations.cjs
# Test Graph call manually (after setting up Azure policy)
curl -X POST "https://graph.microsoft.com/v1.0/users/scheduling@acme.com/onlineMeetings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject":"Test","startDateTime":"2026-04-24T10:00:00Z","endDateTime":"2026-04-24T10:30:00Z"}'
Gotchas
/me/onlineMeetingsvs/users/{upn}/onlineMeetings—/merequires a delegated user token; we use app-only auth so we must use/users/{upn}. Mistakenly using/meis a common Graph pitfall for server-side integrations.OnlineMeetings.ReadWrite.Allis application permission, not delegated. Delegated has a subtly different name.- Timezone conversion:
requested_date+requested_timeon appointment_requests is the requester's local wall-clock. Graph requires ISO 8601 UTC. Must convert usingfromZonedTime(local, requester_timezone)before POST. - Citus distribution:
appointment_requestsis distributed bytenant; all three new columns are non-key data columns, so migration is fine. - Email template storage: templates are DB rows in
system_email_templates, not files. Adding the Handlebars{{#if onlineMeetingUrl}}block requires a new migration that does an UPDATE, not a code change. Use the pattern from20251209175019_update_appointment_email_templates_modern_styling.cjs. - Client portal
appointmentRequestActions.tsis where unauthenticated-to-authenticated appointment handling lives; public-API created appointments still come through/server/src/app/api/public/appointment-request/route.tsand must not regress. /updateAppointmentRequestDateTimealready exists for pending requests; for approved requests the analogous update happens through a different path (schedule-entry edit). Need to check both entry points emit the PATCH.
Related prior context
- Improvement 1 (requester info display) was shipped in the same working session in
AppointmentRequestsPanel.tsxandEntryPopup.tsx— the patterns for conditional rendering onappointmentRequestDataare now established there and should be reused for the Join button. - Improvement 1 locale keys added under
requests.detail.labels.requesterandentryPopup.appointmentRequest.requesterInfo.*— new Teams keys follow the same neighborhood.
Links / references
- Microsoft Graph docs: https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings
- Application Access Policy: https://learn.microsoft.com/en-us/graph/cloud-communication-online-meeting-application-access-policy
- Our Teams integration data model (this repo):
ee/server/migrations/20260307153000_create_teams_integrations.cjs
Progress log
- 2026-04-23: PRD, features.json, tests.json, SCRATCHPAD created in
ee/docs/plans/2026-04-23-teams-meeting-on-appointment-approval/. No implementation yet. - 2026-04-23: Completed
F001by addingserver/migrations/20260423130000_add_online_meeting_columns_to_appointment_requests.cjs. Used guardedhasColumnchecks so the migration is idempotent and safe to re-run in local/dev environments. - 2026-04-23: Completed
F002by addingee/server/migrations/20260423131000_add_default_meeting_organizer_to_teams_integrations.cjs. Kept it EE-local and nullable so tenant setup remains opt-in. - 2026-04-23: Completed
F003by addingcreateTeamsMeeting()plus shared meeting config and Graph auth helpers underee/packages/microsoft-teams/src/lib/meetings/. The helper readsteams_integrations.default_meeting_organizer_upn, resolves the tenant Microsoft profile, POSTs to Graph, logs success/failure, and returnsnullon any soft failure. - 2026-04-23: Completed
F004by addingupdateTeamsMeeting()in the same EE meetings module. It reuses the shared tenant readiness/config resolver, PATCHes Graph with the new ISO datetimes, returnsfalseon soft failure, and emits structured update logs. - 2026-04-23: Completed
F005by addingdeleteTeamsMeeting()in the EE meetings module. It issues a Graph DELETE against the organizer-scoped online meeting endpoint, logs failures without throwing, and returnsfalseon any soft failure. - 2026-04-23: Completed
F006by addinggetTeamsMeetingCapability(tenantId)underee/packages/microsoft-teams/src/lib/actions/meetings/. Capability follows the PRD contract:ee_disabledoutside EE,not_configuredwhen Teams is inactive or missing a selected profile,no_organizerwhen the organizer UPN is blank, otherwiseavailable: true. - 2026-04-23: Completed
F007by addingpackages/scheduling/src/lib/teamsMeetingService.ts. The scheduling package now uses a local EE dynamic-import boundary that returns CE-safe no-op handlers when enterprise code is unavailable or fails to load. - 2026-04-23: Completed
F008by extending the scheduling approval schema withgenerate_teams_meetingdefaulting tofalse. This keeps existing approval callers backward compatible while allowing the UI to opt in explicitly when capability is available. - 2026-04-23: Completed
F009by wiringapproveAppointmentRequest()to the scheduling Teams meeting service. Whengenerate_teams_meetingis true it now creates the meeting, persistsonline_meeting_*columns on success, and threads the join URL into the outgoing approval/assignment email payloads plus the ICS event URL. - 2026-04-23: Completed
F010by extending approval responses withteamsMeetingWarning. The action now distinguishes capability/setup issues from Graph create failures and still returnssuccess: truewhen the appointment itself was approved. - 2026-04-23: Completed
F011by extendingupdateAppointmentRequestDateTime()to support approved requests, update linked schedule entries, publishSCHEDULE_ENTRY_UPDATEDfor approved reschedules, and PATCH an existing Teams meeting whenonline_meeting_idis present. PATCH failures now surface throughteamsMeetingWarning. - 2026-04-23: Completed
F012by wiring Teams meeting deletion into both client-portal cancellation and MSP-side schedule-entry deletion. Both paths now clearonline_meeting_*metadata fromappointment_requests, sever the schedule-entry link, and attempt a best-effort Graph DELETE when the stored provider isteams. - 2026-04-23: Completed
F013by adding conditional Teams deletion warnings to the client-portal cancel confirmations and MSP delete dialogs. Approved appointments can now be cancelled from the client portal, and delete confirmations explicitly warn when the linked Teams meeting will also be removed. - 2026-04-23: Completed
F014by adding a scheduling-sidegetTeamsMeetingCapabilityaction wrapper and wiringAppointmentRequestsPanelto it. The approval form now shows a default-on Teams toggle only when the current tenant is capable of generating meetings. - 2026-04-23: Completed
F015by mirroring the same capability-gated, default-on Teams toggle inside the pending-request approval branch ofEntryPopup. - 2026-04-23: Completed
F016by adding a "Join Teams Meeting" CTA to the approved appointment banner inEntryPopupwhenonline_meeting_urlis present. - 2026-04-23: Completed
F017by exposing the stored Teams meeting URL and a "Join Teams Meeting" button in theAppointmentRequestsPaneldetail view for approved requests. - 2026-04-23: Completed
F018by adding a primary "Join Teams Meeting" button topackages/client-portal/src/components/appointments/AppointmentRequestDetailsPage.tsxwhenonline_meeting_urlis populated. - 2026-04-23: Completed
F019by adding a conditional "Teams Meetings" tab toAvailabilitySettings.tsx. Visibility is driven by a new scheduling action that checks whetherteams_integrationsexists and whether the tenant's install status isactive. - 2026-04-23: Completed
F020by replacing the Teams Meetings tab placeholder with a real organizer form inAvailabilitySettings.tsx: prerequisites banner, organizer UPN input, Save action, Verify button, and inline verification feedback. - 2026-04-23: Completed
F021by addingsetDefaultMeetingOrganizerinavailabilitySettingsActions.ts. The action is CE-safe, enforcessystem_settings:update, verifies that Teams is active for the tenant, and writes the trimmed organizer UPN (orNULLwhen cleared). - 2026-04-23: Completed
F022by addingverifyMeetingOrganizerin scheduling plus an EE Graph helper. Verification first resolves/users/{upn}, then performs a short create/delete round-trip so missing Application Access Policy cases come back asreason: 'policy_missing'. - 2026-04-23: Completed
F024by extending@alga-psa/emailappointment payload types withonlineMeetingUrland wiring the approved-email fallback template to render a Teams join action when the URL is present. - 2026-04-23: Completed
F025by changing approval-email ICS generation to emitLOCATION: Microsoft Teams Meeting,URL: <join link>, and aJoin Teams Meeting: ...description line only when a Teams meeting was created. - 2026-04-23: Completed
F023by updating the source-of-truth appointment email templates plus a new re-upsert migration so both approved-client and assigned-technician emails render a conditionalJoin Teams Meetingblock whenonlineMeetingUrlis populated. - 2026-04-23: Completed
F026by adding the new Teams UI keys to English locale files, backfilling the same keys across shipped human locales to satisfy the translation validator, and regenerating pseudo-locales. - 2026-04-23: Completed
F027by authoring the Azure admin runbook atdocs/integrations/teams-meetings-setup.md, adding a browser-served copy underserver/public/docs/..., and repointing the Availability Settings banner link to that runbook. - 2026-04-23: Completed
F028by normalizing Teams meeting helper logs so create/update/delete success paths log structured INFO rows and non-response warning paths still includestatus: nullalongside tenant, request, and operation context. - 2026-04-23: Completed
T019withpackages/scheduling/tests/appointmentRequestSchemas.test.ts, coveringgenerate_teams_meetingexplicit true/false values plus omission defaulting tofalse. - 2026-04-23: Completed
T046by extendingserver/src/test/unit/icsGenerator.test.tswith an explicit Teams meeting case that asserts bothLOCATION: Microsoft Teams Meetingand the joinURL:line are emitted together. - 2026-04-23: Completed
T047by tightening the minimal ICS generator test so events without a meeting URL assert bothLOCATIONandURLare absent. - 2026-04-23: Completed
T048by re-runningnode scripts/generate-pseudo-locales.cjsandnode scripts/validate-translations.cjs, ending with validator outputErrors: 0andWarnings: 0. - 2026-04-23: Completed
T049withpackages/scheduling/tests/teamsMeetingsRunbook.contract.test.ts, asserting the canonical runbook content exists and the Availability Settings banner opens the browser-served copy. - 2026-04-23: Completed
T044withserver/src/test/unit/appointmentEmailTemplates.test.ts, rendering the approved-client Handlebars template directly and asserting the Teams join button appears only whenonlineMeetingUrlis provided. - 2026-04-23: Completed
T045by extending the same template-render suite to the assigned-technician template and asserting the technician email includes the Teams join button whenonlineMeetingUrlis present. - 2026-04-23: Completed
T003-T016,T018, andT051withserver/src/test/unit/teamsMeetingHelpers.test.ts, covering Teams meeting create/update/delete helper behavior, capability resolution, the CE no-op shim, and tenant-scoped credential isolation. - 2026-04-23: Completed
T043withpackages/scheduling/tests/availabilitySettingsActions.permission.test.ts, assertingsetDefaultMeetingOrganizerrejects callers who lack the settings-update permission. - 2026-04-23: Completed
T040-T042withserver/src/test/unit/verifyMeetingOrganizer.test.ts, covering successful organizer verification, missing-user handling, and Application Access Policy failures that map toreason: 'policy_missing'. - 2026-04-23: Completed
T001-T002withserver/src/test/unit/teamsMeetingMigrations.test.ts, executing both migrationup()functions against mockedknex.schemasurfaces and asserting the new columns are added as nullabletext. - 2026-04-23: Completed
T017by hardening CE build wiring and rerunningnpm run build:ce. Rootbuild/build:cescripts now forceEDITION=community NEXT_PUBLIC_EDITION=community, andserver/next.config.mjsmaps@alga-psa/ee-microsoft-teamsto./src/emptyfor CE so the built manifest no longer points atee/packages/microsoft-teams. - 2026-04-24: Completed
T053by rerunningnpm run build:ceafter the Teams meeting changes landed. The community build completed end-to-end, confirming the existing EE dynamic-import guard still keeps CE builds green. - 2026-04-24: Completed
T020withserver/src/test/integration/appointmentRequests.integration.test.ts, adding approval-path integration coverage that asserts a Teams-enabled approval persistsonline_meeting_provider/url/idand passesonlineMeetingUrlinto the approved-email payload. - 2026-04-24: Completed
T021in the same approval integration suite, assertinggenerate_teams_meeting: falseleavesonline_meeting_*columnsNULLand never calls the Teams meeting service. - 2026-04-24: Completed
T022in the same suite, asserting approval still succeeds when Teams capability is unavailable, no Graph create runs, andteamsMeetingWarningexplains the organizer/setup gap. - 2026-04-24: Completed
T023in the same suite, asserting a soft Graph create failure leaves the appointment approved, keeps meeting columns empty, and omitsonlineMeetingUrlfrom the approval email payload. - 2026-04-24: Completed
T052in the same approval suite, asserting requester-local wall-clock times are converted to the correct UTC instants beforecreateTeamsMeeting()is called. - 2026-04-24: Completed
T024in the same integration file, asserting approved reschedules callupdateTeamsMeeting()with the expected new UTC start/end timestamps for the stored Teams meeting. - 2026-04-24: Completed
T025in the same reschedule integration slice, asserting a soft Graph PATCH failure still returnssuccess: trueand surfacesteamsMeetingWarningto the caller. - 2026-04-24: Completed
T026in the same reschedule integration slice, asserting approved requests withoutonline_meeting_idreschedule normally without calling the Teams meeting service. - 2026-04-24: Completed
T027in the deletion lifecycle integration slice, asserting client-side cancellation of an approved appointment callsdeleteTeamsMeeting()with the stored Teams meeting ID. - 2026-04-24: Completed
T028in the same deletion lifecycle slice, asserting MSP schedule-entry deletion also callsdeleteTeamsMeeting()for approved appointments that carry Teams metadata. - 2026-04-24: Completed
T029by fixingcancelAppointmentRequest()to returnteamsMeetingWarningwhen the best-effort Teams DELETE fails, then asserting the cancellation still succeeds and surfaces that warning. - 2026-04-24: Completed
T030withserver/src/test/unit/appointments/AppointmentRequestDetailsPage.teams.test.tsx, asserting the client-portal cancel confirmation includes the Teams deletion warning whenonline_meeting_urlis present. - 2026-04-24: Completed
T031in the same client-portal detail test file, asserting the cancel confirmation falls back to the normal message when there is no Teams meeting URL. - 2026-04-24: Completed
T032withserver/src/test/unit/appointments/AppointmentRequestsPanel.teams.test.tsx, asserting the MSP appointment-requests approval form shows a checked Teams toggle when capability is available. - 2026-04-24: Completed
T033in the same appointment-requests panel test file, asserting the approval form hides the Teams toggle when capability reports unavailable. - 2026-04-24: Completed
T034withserver/src/test/unit/appointments/EntryPopup.teams.test.tsx, asserting the pending appointment-request branch ofEntryPopupshows the Teams toggle when capability is available. - 2026-04-24: Completed
T035in the sameEntryPopuptest file, asserting the approved appointment banner renders the Teams join action whenonline_meeting_urlis present. - 2026-04-24: Completed
T036in theAppointmentRequestsPaneltest file, asserting approved request details render the Teams join action when a meeting URL is stored. - 2026-04-24: Completed
T037in the client-portal detail page test file, assertingAppointmentRequestDetailsPagerenders theJoin Teams Meetingbutton whenonline_meeting_urlis populated. - 2026-04-24: Completed
T038withserver/src/test/unit/appointments/AvailabilitySettings.teams.test.tsx, asserting theTeams Meetingstab is rendered only when the tab-state action reports it visible. - 2026-04-24: Completed
T039in the same Availability Settings test file, asserting the organizer UPN input saves throughsetDefaultMeetingOrganizer()and persists the returned organizer value in the form. - 2026-04-24: Completed
T050inserver/src/test/unit/teamsMeetingHelpers.test.ts, asserting a create/update/delete helper lifecycle emits three structured INFO logs with tenant, appointment request ID, operation, and status. - 2026-04-24: Completed
T054by runningnpm run build:ee. The enterprise production build completed successfully, confirming the new Teams meeting helpers and UI surfaces all survive the full EE type-check and bundle pipeline together.
Working notes
- Migration pattern for
appointment_requestsfollows the existing guardedalterTablestyle from20260416210000_add_requester_timezone_to_appointment_requests.cjs. teams_integrationsmigration does not need a Citus redistribution step for a nullable non-key column; guardedalterTableis sufficient.- Extracted Graph app-token acquisition into
ee/packages/microsoft-teams/src/lib/graphAuth.tsso meeting helpers and notification delivery share the same OAuth client-credentials flow. - Added
resolveTeamsMeetingExecutionConfig()as the central place for readiness checks needed by create/update/delete helpers: install status must beactive,selected_profile_idmust exist, organizer UPN must be set, and the Microsoft profile must resolve asready. - Scheduling-side EE boundary mirrors the calendar-actions pattern: memoized dynamic import of
@alga-psa/ee-microsoft-teams/lib, fallback logging on load failure, and CE no-op implementations instead of hard EE imports. - Client portal needed its own EE-safe Teams delete loader because it does not depend on the scheduling package. Implemented a local guarded dynamic import in
packages/client-portal/src/actions/client-portal-actions/appointmentRequestActions.ts. DeleteEntityDialognow accepts an optionalconfirmationMessage, which keeps dependency-validation behavior intact while allowing schedule-entry delete flows to surface appointment-specific warnings like the Teams-meeting deletion notice.verifyMeetingOrganizerrequired a second meeting-config resolver path:resolveTeamsMeetingGraphConfig()handles tenants with an active Teams profile even before an organizer is saved, whileresolveTeamsMeetingExecutionConfig()still enforces the organizer requirement for create/update/delete flows.- Teams Meetings settings UI currently links admins to Microsoft’s Application Access Policy documentation directly; when the local runbook is added later (
F027), update this banner link to the repo-authored setup guide. SystemEmailService.sendAppointmentAssignedNotification()already passes the full payload object throughreplaceVariables(), soonlineMeetingUrlonly needed a type update there; the explicit fallback template change was only necessary for the approved-client email path.- The previous ICS behavior always included a client-portal URL and tenant-name location. Teams-meeting acceptance criteria required narrowing that behavior so URL/location are only emitted for actual online meetings.
- Email template updates follow the repo’s source-of-truth pattern: modify
server/migrations/utils/templates/email/appointments/*.cjsand add a migration that re-upserts existing DB rows, rather than editing seeded SQL or relying on future installs only. - Translation validator enforces new keys across all shipped human locales (
de/es/fr/it/nl/pl/pt) as well as pseudo locales. Adding English-only keys is not enough to makenode scripts/validate-translations.cjspass. - Repo docs under
docs/are not automatically browser-accessible from the MSP UI. For in-app documentation links, a static copy underserver/public/...is needed unless a dedicated docs route already exists. - Teams meeting helper logs already covered Graph response success/failure. The final observability cleanup was making the non-response warning paths structurally consistent by always including a
statusfield as well. - Approval flow nuance: when the approver keeps the originally requested time,
requested_date/requested_timemust be converted from the requester's local wall clock viafromZonedTime(...); only explicitfinal_date/final_timevalues sent from the UI are already normalized to UTC strings. - Integration harness nuance: initialize the Teams meeting mocks in
beforeEach, not onlyafterEach, so isolated Vitest filters still see a configuredgetTeamsMeetingCapability()on the first matching test. - Client-portal cancel nuance: Teams deletion is intentionally fail-soft, but the action still needs to propagate a warning string back to the UI when
deleteTeamsMeetingIfAvailable()returnsfalse; otherwise the failure is silent even though the appointment cancellation succeeded. - CE regression check on 2026-04-24 passed on the normal
build:cepath, so the existingEDITION=communityforcing plus@alga-psa/ee-microsoft-teams -> ./src/emptyaliasing remains sufficient to keep enterprise-only code out of the CE bundle. - EE regression check on 2026-04-24 passed on the normal
build:eepath; the build still emitted the repo's long-standing webpack warnings, but there were no new type or bundle failures from the Teams meeting work. - The server integration harness needed both
@alga-psa/dband@alga-psa/auth-layer synchronization. Mocking onlyserver/src/lib/dband@alga-psa/users/actionsis insufficient now that these actions import package-level wrappers directly. - Approval-path integration tests also need UUID-shaped staff fixture IDs because
appointment_requests.approved_by_user_idis typed asuuid; old placeholder strings caused PostgreSQL22P02failures before Teams assertions ran. - Runbook command used for validation so far:
node -c server/migrations/20260423130000_add_online_meeting_columns_to_appointment_requests.cjs - Additional validation command:
node -c ee/server/migrations/20260423131000_add_default_meeting_organizer_to_teams_integrations.cjs - Additional validation command:
npm -w ee/packages/microsoft-teams run typecheck - Additional validation command:
npm -w packages/scheduling run typecheck - Additional validation command:
node scripts/generate-pseudo-locales.cjs - Additional validation command:
node scripts/validate-translations.cjs - Additional validation command:
npx vitest --root packages/scheduling --config vitest.config.ts run tests/appointmentRequestSchemas.test.ts - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/icsGenerator.test.ts --coverage.enabled false - Additional validation command:
npx vitest --root packages/scheduling --config vitest.config.ts run tests/teamsMeetingsRunbook.contract.test.ts - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/appointmentEmailTemplates.test.ts --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/teamsMeetingHelpers.test.ts --coverage.enabled false - Additional validation command:
npx vitest --root packages/scheduling --config vitest.config.ts run tests/availabilitySettingsActions.permission.test.ts - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/verifyMeetingOrganizer.test.ts --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/teamsMeetingMigrations.test.ts --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/integration/appointmentRequests.integration.test.ts -t "creates and stores a Teams meeting when approval opts in|skips Teams meeting creation when the toggle is off|returns a warning and skips Graph create when Teams capability is unavailable|keeps approval successful when Teams meeting creation fails|converts requester-local approval times to UTC before creating the Teams meeting" --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/integration/appointmentRequests.integration.test.ts -t "reschedules the linked Teams meeting when an approved request has an online meeting|returns a warning when the Teams reschedule PATCH fails|does not call Teams when the approved request has no online meeting id" --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/integration/appointmentRequests.integration.test.ts -t "deletes the linked Teams meeting when a client cancels an approved appointment|deletes the linked Teams meeting when MSP staff deletes the schedule entry|surfaces a warning when Teams meeting deletion fails during cancellation" --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/appointments/AppointmentRequestDetailsPage.teams.test.tsx --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/appointments/AppointmentRequestsPanel.teams.test.tsx --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/appointments/EntryPopup.teams.test.tsx --coverage.enabled false - Additional validation command:
npx vitest --root server --config vitest.config.ts run src/test/unit/appointments/AvailabilitySettings.teams.test.tsx --coverage.enabled false - Additional validation command:
rm -rf server/.next && npm run build:ce - Additional validation command:
rg -n "@alga-psa/ee-microsoft-teams|ee/packages/microsoft-teams" server/.next - Additional validation command:
npm run build:ce - Additional validation command:
npm run build:ee