elevenlabs-webhook-nodejs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +27 -0
- package/.dockerignore +6 -0
- package/.env.example +43 -0
- package/BEFORE-PRODUCTION.md +32 -0
- package/Dockerfile +23 -0
- package/SYSTEM_OVERVIEW.md +942 -0
- package/SYSTEM_STATUS.md +332 -0
- package/backup_script/main.py +146 -0
- package/baileys/.dockerignore +7 -0
- package/baileys/Dockerfile +13 -0
- package/baileys/README.md +412 -0
- package/baileys/index.js +499 -0
- package/baileys/package-lock.json +2532 -0
- package/baileys/package.json +25 -0
- package/baileys/server.js +96 -0
- package/baileys/src/config.js +55 -0
- package/baileys/src/middleware/api-key.js +16 -0
- package/baileys/src/routes/accounts.js +51 -0
- package/baileys/src/routes/chats.js +103 -0
- package/baileys/src/routes/webhooks.js +34 -0
- package/baileys/src/services/account-store.js +259 -0
- package/baileys/src/services/webhook-dispatcher.js +161 -0
- package/baileys/src/services/worker-manager.js +597 -0
- package/baileys/src/utils/jid.js +79 -0
- package/baileys/src/utils/logger.js +16 -0
- package/baileys/src/utils/use-mongodb-auth-state.js +122 -0
- package/baileys/worker.js +721 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +84 -0
- package/dist/app.js.map +1 -0
- package/dist/config/agentPrompts.json +19 -0
- package/dist/config/database.d.ts +1 -0
- package/dist/config/database.js +26 -0
- package/dist/config/database.js.map +1 -0
- package/dist/config/env.d.ts +41 -0
- package/dist/config/env.js +81 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +73 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controllers/webhook.controller.d.ts +10 -0
- package/dist/controllers/webhook.controller.js +128 -0
- package/dist/controllers/webhook.controller.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/hooks/webhookValidator.d.ts +2 -0
- package/dist/hooks/webhookValidator.js +47 -0
- package/dist/hooks/webhookValidator.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/001_session-architecture.d.ts +3 -0
- package/dist/migrations/001_session-architecture.js +119 -0
- package/dist/migrations/001_session-architecture.js.map +1 -0
- package/dist/migrations/002_agent-ref.d.ts +3 -0
- package/dist/migrations/002_agent-ref.js +55 -0
- package/dist/migrations/002_agent-ref.js.map +1 -0
- package/dist/migrations/003_shift-schedule.d.ts +3 -0
- package/dist/migrations/003_shift-schedule.js +48 -0
- package/dist/migrations/003_shift-schedule.js.map +1 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.d.ts +3 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.js +27 -0
- package/dist/migrations/004_invert-shift-to-clinic-hours.js.map +1 -0
- package/dist/migrations/005_composite-baileys-chat-ids.d.ts +3 -0
- package/dist/migrations/005_composite-baileys-chat-ids.js +47 -0
- package/dist/migrations/005_composite-baileys-chat-ids.js.map +1 -0
- package/dist/migrations/migration.model.d.ts +18 -0
- package/dist/migrations/migration.model.js +45 -0
- package/dist/migrations/migration.model.js.map +1 -0
- package/dist/migrations/migration.routes.d.ts +2 -0
- package/dist/migrations/migration.routes.js +37 -0
- package/dist/migrations/migration.routes.js.map +1 -0
- package/dist/migrations/migration.runner.d.ts +19 -0
- package/dist/migrations/migration.runner.js +74 -0
- package/dist/migrations/migration.runner.js.map +1 -0
- package/dist/models/ConversationClaim.d.ts +13 -0
- package/dist/models/ConversationClaim.js +44 -0
- package/dist/models/ConversationClaim.js.map +1 -0
- package/dist/models/ConversationEvaluation.d.ts +23 -0
- package/dist/models/ConversationEvaluation.js +92 -0
- package/dist/models/ConversationEvaluation.js.map +1 -0
- package/dist/models/Summary.d.ts +37 -0
- package/dist/models/Summary.js +78 -0
- package/dist/models/Summary.js.map +1 -0
- package/dist/models/Transcription.d.ts +34 -0
- package/dist/models/Transcription.js +71 -0
- package/dist/models/Transcription.js.map +1 -0
- package/dist/models/WhatsAppRecipient.d.ts +23 -0
- package/dist/models/WhatsAppRecipient.js +53 -0
- package/dist/models/WhatsAppRecipient.js.map +1 -0
- package/dist/modules/admin/admin.routes.d.ts +2 -0
- package/dist/modules/admin/admin.routes.js +1966 -0
- package/dist/modules/admin/admin.routes.js.map +1 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.d.ts +2 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.js +158 -0
- package/dist/modules/admin/elevenlabs-test-chat.routes.js.map +1 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.d.ts +2 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.js +204 -0
- package/dist/modules/admin/whatsapp-test-chat.routes.js.map +1 -0
- package/dist/modules/agents/agent.model.d.ts +38 -0
- package/dist/modules/agents/agent.model.js +92 -0
- package/dist/modules/agents/agent.model.js.map +1 -0
- package/dist/modules/agents/agent.routes.d.ts +2 -0
- package/dist/modules/agents/agent.routes.js +61 -0
- package/dist/modules/agents/agent.routes.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.model.d.ts +76 -0
- package/dist/modules/appointment-validation/appointment-validation.model.js +118 -0
- package/dist/modules/appointment-validation/appointment-validation.model.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.d.ts +2 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.js +202 -0
- package/dist/modules/appointment-validation/appointment-validation.routes.js.map +1 -0
- package/dist/modules/appointment-validation/appointment-validation.service.d.ts +53 -0
- package/dist/modules/appointment-validation/appointment-validation.service.js +827 -0
- package/dist/modules/appointment-validation/appointment-validation.service.js.map +1 -0
- package/dist/modules/auth/auth.model.d.ts +17 -0
- package/dist/modules/auth/auth.model.js +64 -0
- package/dist/modules/auth/auth.model.js.map +1 -0
- package/dist/modules/auth/auth.routes.d.ts +2 -0
- package/dist/modules/auth/auth.routes.js +202 -0
- package/dist/modules/auth/auth.routes.js.map +1 -0
- package/dist/modules/auth/auth.service.d.ts +28 -0
- package/dist/modules/auth/auth.service.js +183 -0
- package/dist/modules/auth/auth.service.js.map +1 -0
- package/dist/modules/auth/refresh-token.model.d.ts +13 -0
- package/dist/modules/auth/refresh-token.model.js +52 -0
- package/dist/modules/auth/refresh-token.model.js.map +1 -0
- package/dist/modules/billing/billing-alert.model.d.ts +16 -0
- package/dist/modules/billing/billing-alert.model.js +47 -0
- package/dist/modules/billing/billing-alert.model.js.map +1 -0
- package/dist/modules/billing/billing-period-snapshot.model.d.ts +35 -0
- package/dist/modules/billing/billing-period-snapshot.model.js +68 -0
- package/dist/modules/billing/billing-period-snapshot.model.js.map +1 -0
- package/dist/modules/billing/billing.model.d.ts +18 -0
- package/dist/modules/billing/billing.model.js +62 -0
- package/dist/modules/billing/billing.model.js.map +1 -0
- package/dist/modules/billing/billing.routes.d.ts +2 -0
- package/dist/modules/billing/billing.routes.js +63 -0
- package/dist/modules/billing/billing.routes.js.map +1 -0
- package/dist/modules/billing/billing.service.d.ts +69 -0
- package/dist/modules/billing/billing.service.js +498 -0
- package/dist/modules/billing/billing.service.js.map +1 -0
- package/dist/modules/billing/payment.model.d.ts +24 -0
- package/dist/modules/billing/payment.model.js +57 -0
- package/dist/modules/billing/payment.model.js.map +1 -0
- package/dist/modules/calls/call.model.d.ts +41 -0
- package/dist/modules/calls/call.model.js +97 -0
- package/dist/modules/calls/call.model.js.map +1 -0
- package/dist/modules/calls/call.routes.d.ts +2 -0
- package/dist/modules/calls/call.routes.js +103 -0
- package/dist/modules/calls/call.routes.js.map +1 -0
- package/dist/modules/campaigns/campaign.model.d.ts +45 -0
- package/dist/modules/campaigns/campaign.model.js +98 -0
- package/dist/modules/campaigns/campaign.model.js.map +1 -0
- package/dist/modules/campaigns/campaign.routes.d.ts +2 -0
- package/dist/modules/campaigns/campaign.routes.js +323 -0
- package/dist/modules/campaigns/campaign.routes.js.map +1 -0
- package/dist/modules/campaigns/campaign.service.d.ts +11 -0
- package/dist/modules/campaigns/campaign.service.js +86 -0
- package/dist/modules/campaigns/campaign.service.js.map +1 -0
- package/dist/modules/google-calendar/google-calendar.routes.d.ts +2 -0
- package/dist/modules/google-calendar/google-calendar.routes.js +32 -0
- package/dist/modules/google-calendar/google-calendar.routes.js.map +1 -0
- package/dist/modules/inbound-call/inbound-call-config.model.d.ts +20 -0
- package/dist/modules/inbound-call/inbound-call-config.model.js +68 -0
- package/dist/modules/inbound-call/inbound-call-config.model.js.map +1 -0
- package/dist/modules/inbound-call/inbound-call.routes.d.ts +2 -0
- package/dist/modules/inbound-call/inbound-call.routes.js +243 -0
- package/dist/modules/inbound-call/inbound-call.routes.js.map +1 -0
- package/dist/modules/leads/lead.model.d.ts +24 -0
- package/dist/modules/leads/lead.model.js +54 -0
- package/dist/modules/leads/lead.model.js.map +1 -0
- package/dist/modules/leads/lead.routes.d.ts +2 -0
- package/dist/modules/leads/lead.routes.js +201 -0
- package/dist/modules/leads/lead.routes.js.map +1 -0
- package/dist/modules/plans/plan.model.d.ts +25 -0
- package/dist/modules/plans/plan.model.js +59 -0
- package/dist/modules/plans/plan.model.js.map +1 -0
- package/dist/modules/surveys/survey.model.d.ts +30 -0
- package/dist/modules/surveys/survey.model.js +75 -0
- package/dist/modules/surveys/survey.model.js.map +1 -0
- package/dist/modules/surveys/survey.routes.d.ts +2 -0
- package/dist/modules/surveys/survey.routes.js +153 -0
- package/dist/modules/surveys/survey.routes.js.map +1 -0
- package/dist/modules/tenants/tenant.model.d.ts +86 -0
- package/dist/modules/tenants/tenant.model.js +127 -0
- package/dist/modules/tenants/tenant.model.js.map +1 -0
- package/dist/modules/tenants/tenant.routes.d.ts +2 -0
- package/dist/modules/tenants/tenant.routes.js +65 -0
- package/dist/modules/tenants/tenant.routes.js.map +1 -0
- package/dist/modules/users/user.routes.d.ts +2 -0
- package/dist/modules/users/user.routes.js +106 -0
- package/dist/modules/users/user.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.d.ts +20 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.js +85 -0
- package/dist/modules/webhooks/elevenlabs-tool.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.d.ts +11 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.js +360 -0
- package/dist/modules/webhooks/elevenlabs-tool.service.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs.routes.d.ts +2 -0
- package/dist/modules/webhooks/elevenlabs.routes.js +34 -0
- package/dist/modules/webhooks/elevenlabs.routes.js.map +1 -0
- package/dist/modules/webhooks/elevenlabs.service.d.ts +6 -0
- package/dist/modules/webhooks/elevenlabs.service.js +512 -0
- package/dist/modules/webhooks/elevenlabs.service.js.map +1 -0
- package/dist/modules/webhooks/unipile.routes.d.ts +2 -0
- package/dist/modules/webhooks/unipile.routes.js +780 -0
- package/dist/modules/webhooks/unipile.routes.js.map +1 -0
- package/dist/modules/whatsapp/appointment.model.d.ts +27 -0
- package/dist/modules/whatsapp/appointment.model.js +58 -0
- package/dist/modules/whatsapp/appointment.model.js.map +1 -0
- package/dist/modules/whatsapp/operator-request.model.d.ts +29 -0
- package/dist/modules/whatsapp/operator-request.model.js +65 -0
- package/dist/modules/whatsapp/operator-request.model.js.map +1 -0
- package/dist/modules/whatsapp/stt-usage.model.d.ts +16 -0
- package/dist/modules/whatsapp/stt-usage.model.js +53 -0
- package/dist/modules/whatsapp/stt-usage.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.d.ts +23 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.js +55 -0
- package/dist/modules/whatsapp/whatsapp-chat.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.d.ts +23 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.js +54 -0
- package/dist/modules/whatsapp/whatsapp-contact-profile.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-message.model.d.ts +30 -0
- package/dist/modules/whatsapp/whatsapp-message.model.js +52 -0
- package/dist/modules/whatsapp/whatsapp-message.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp-session.model.d.ts +33 -0
- package/dist/modules/whatsapp/whatsapp-session.model.js +65 -0
- package/dist/modules/whatsapp/whatsapp-session.model.js.map +1 -0
- package/dist/modules/whatsapp/whatsapp.routes.d.ts +2 -0
- package/dist/modules/whatsapp/whatsapp.routes.js +1237 -0
- package/dist/modules/whatsapp/whatsapp.routes.js.map +1 -0
- package/dist/plugins/cors.d.ts +3 -0
- package/dist/plugins/cors.js +11 -0
- package/dist/plugins/cors.js.map +1 -0
- package/dist/plugins/jwt.d.ts +3 -0
- package/dist/plugins/jwt.js +22 -0
- package/dist/plugins/jwt.js.map +1 -0
- package/dist/plugins/rawBody.d.ts +3 -0
- package/dist/plugins/rawBody.js +16 -0
- package/dist/plugins/rawBody.js.map +1 -0
- package/dist/plugins/rbac.d.ts +5 -0
- package/dist/plugins/rbac.js +29 -0
- package/dist/plugins/rbac.js.map +1 -0
- package/dist/routes/admin.routes.d.ts +2 -0
- package/dist/routes/admin.routes.js +169 -0
- package/dist/routes/admin.routes.js.map +1 -0
- package/dist/routes/health.routes.d.ts +2 -0
- package/dist/routes/health.routes.js +16 -0
- package/dist/routes/health.routes.js.map +1 -0
- package/dist/routes/webhook.routes.d.ts +2 -0
- package/dist/routes/webhook.routes.js +17 -0
- package/dist/routes/webhook.routes.js.map +1 -0
- package/dist/services/ai/base.ai.d.ts +19 -0
- package/dist/services/ai/base.ai.js +120 -0
- package/dist/services/ai/base.ai.js.map +1 -0
- package/dist/services/ai/gemini.service.d.ts +11 -0
- package/dist/services/ai/gemini.service.js +43 -0
- package/dist/services/ai/gemini.service.js.map +1 -0
- package/dist/services/ai/index.d.ts +2 -0
- package/dist/services/ai/index.js +26 -0
- package/dist/services/ai/index.js.map +1 -0
- package/dist/services/ai/openai.service.d.ts +11 -0
- package/dist/services/ai/openai.service.js +50 -0
- package/dist/services/ai/openai.service.js.map +1 -0
- package/dist/services/elevenlabs.service.d.ts +52 -0
- package/dist/services/elevenlabs.service.js +447 -0
- package/dist/services/elevenlabs.service.js.map +1 -0
- package/dist/services/google-calendar.service.d.ts +60 -0
- package/dist/services/google-calendar.service.js +494 -0
- package/dist/services/google-calendar.service.js.map +1 -0
- package/dist/services/inbound-call-schedule.service.d.ts +11 -0
- package/dist/services/inbound-call-schedule.service.js +162 -0
- package/dist/services/inbound-call-schedule.service.js.map +1 -0
- package/dist/services/netgsm.service.d.ts +41 -0
- package/dist/services/netgsm.service.js +89 -0
- package/dist/services/netgsm.service.js.map +1 -0
- package/dist/services/unipile.service.d.ts +41 -0
- package/dist/services/unipile.service.js +149 -0
- package/dist/services/unipile.service.js.map +1 -0
- package/dist/services/whatsapp-agent.service.d.ts +139 -0
- package/dist/services/whatsapp-agent.service.js +2055 -0
- package/dist/services/whatsapp-agent.service.js.map +1 -0
- package/dist/services/whatsapp.service.d.ts +26 -0
- package/dist/services/whatsapp.service.js +206 -0
- package/dist/services/whatsapp.service.js.map +1 -0
- package/dist/templates/index.d.ts +39 -0
- package/dist/templates/index.js +35 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/receptionist.d.ts +2 -0
- package/dist/templates/receptionist.js +39 -0
- package/dist/templates/receptionist.js.map +1 -0
- package/dist/templates/survey.d.ts +2 -0
- package/dist/templates/survey.js +41 -0
- package/dist/templates/survey.js.map +1 -0
- package/dist/types/index.d.ts +173 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/logger.js.map +1 -0
- package/docker-compose.nestjs.yml +89 -0
- package/docker-compose.yml +78 -0
- package/docs/AI_AGENT_ENHANCEMENT_PLAN.md +164 -0
- package/docs/API.md +1193 -0
- package/docs/API_ENDPOINTS.md +344 -0
- package/docs/ARCHITECTURE.md +305 -0
- package/docs/AUTH_API.md +252 -0
- package/docs/BILLING_SMS_ALERTS.md +94 -0
- package/docs/CHAT_ASSIGNMENT_SYSTEM.md +118 -0
- package/docs/CLIENT_TOOLS_AND_FEATURES.md +337 -0
- package/docs/ELEVENLABS_WEBHOOK_TOOLS.md +644 -0
- package/docs/FRONTEND_CHECKLIST.md +227 -0
- package/docs/IMPLEMENTATION_STATUS.md +470 -0
- package/docs/MIGRATION_GUIDE.md +96 -0
- package/docs/MISSINGS_REPORT.md +507 -0
- package/docs/NESTJS_MIGRATION_REFERENCE.md +5136 -0
- package/docs/PROJECT_DESCRIPTION.md +1038 -0
- package/docs/SCALING.md +148 -0
- package/docs/SESSION_SUMMARY_2026_03_17.md +135 -0
- package/docs/WHATSAPP_AGENT.md +1086 -0
- package/docs/architecture/00-SYSTEM-OVERVIEW.md +318 -0
- package/docs/architecture/01-DATABASE-SCHEMA.md +2564 -0
- package/docs/architecture/02-AUTHENTICATION.md +1040 -0
- package/docs/architecture/03-MULTI-CLINIC.md +742 -0
- package/docs/architecture/04-WHATSAPP-AGENT.md +608 -0
- package/docs/architecture/05-OPERATOR-WORKFLOW.md +444 -0
- package/docs/architecture/06-BAILEYS-MICROSERVICE.md +616 -0
- package/docs/architecture/07-APPOINTMENTS.md +849 -0
- package/docs/architecture/08-VOICE-CALLS.md +470 -0
- package/docs/architecture/09-OUTBOUND-CAMPAIGNS.md +542 -0
- package/docs/architecture/10-CLIENT-TOOLS.md +665 -0
- package/docs/architecture/11-BILLING.md +458 -0
- package/docs/architecture/12-SECURITY.md +216 -0
- package/docs/architecture/13-LOGGING-AUDIT.md +549 -0
- package/docs/architecture/14-META-BUSINESS-API.md +454 -0
- package/docs/architecture/15-AI-MODULE.md +479 -0
- package/docs/architecture/16-BACKGROUND-JOBS.md +469 -0
- package/docs/architecture/17-REALTIME-LIVECHAT.md +447 -0
- package/docs/architecture/18-FILE-STORAGE.md +410 -0
- package/docs/architecture/19-PATIENTS.md +1034 -0
- package/docs/architecture/20-TREATMENTS-AND-PLANS.md +774 -0
- package/docs/architecture/21-BEFORE-AFTER-PHOTOS.md +519 -0
- package/docs/database.md +456 -0
- package/docs/ornek-randevu-onay.csv +3 -0
- package/ecosystem.config.js +16 -0
- package/elevenlabs-convai-api-reference.md +1171 -0
- package/frontend/.dockerignore +2 -0
- package/frontend/Dockerfile +24 -0
- package/frontend/README.md +75 -0
- package/frontend/components.json +25 -0
- package/frontend/eslint.config.js +23 -0
- package/frontend/index.html +13 -0
- package/frontend/nginx.conf +37 -0
- package/frontend/package-lock.json +8709 -0
- package/frontend/package.json +71 -0
- package/frontend/public/favicon.svg +1 -0
- package/frontend/public/icons.svg +24 -0
- package/frontend/src/App.tsx +125 -0
- package/frontend/src/components/error-boundary.tsx +50 -0
- package/frontend/src/components/shared/activity-timeline.tsx +66 -0
- package/frontend/src/components/shared/appointment-calendar.css +80 -0
- package/frontend/src/components/shared/appointment-calendar.tsx +245 -0
- package/frontend/src/components/shared/chat-bubble.tsx +72 -0
- package/frontend/src/components/shared/combobox.tsx +119 -0
- package/frontend/src/components/shared/confirm-dialog.tsx +57 -0
- package/frontend/src/components/shared/data-table-pagination.tsx +97 -0
- package/frontend/src/components/shared/data-table-toolbar.tsx +39 -0
- package/frontend/src/components/shared/empty-state.tsx +27 -0
- package/frontend/src/components/shared/file-upload.tsx +140 -0
- package/frontend/src/components/shared/index.ts +20 -0
- package/frontend/src/components/shared/language-switcher.tsx +29 -0
- package/frontend/src/components/shared/notification-dropdown.tsx +115 -0
- package/frontend/src/components/shared/page-header.tsx +23 -0
- package/frontend/src/components/shared/stat-card.tsx +37 -0
- package/frontend/src/components/shared/status-badge.tsx +70 -0
- package/frontend/src/components/shared/status-dot.tsx +43 -0
- package/frontend/src/components/ui/alert-dialog.tsx +187 -0
- package/frontend/src/components/ui/alert.tsx +76 -0
- package/frontend/src/components/ui/avatar.tsx +109 -0
- package/frontend/src/components/ui/badge.tsx +52 -0
- package/frontend/src/components/ui/breadcrumb.tsx +125 -0
- package/frontend/src/components/ui/button.tsx +60 -0
- package/frontend/src/components/ui/calendar.tsx +219 -0
- package/frontend/src/components/ui/card.tsx +103 -0
- package/frontend/src/components/ui/chart.tsx +371 -0
- package/frontend/src/components/ui/checkbox.tsx +29 -0
- package/frontend/src/components/ui/collapsible.tsx +19 -0
- package/frontend/src/components/ui/command.tsx +194 -0
- package/frontend/src/components/ui/dialog.tsx +158 -0
- package/frontend/src/components/ui/dropdown-menu.tsx +268 -0
- package/frontend/src/components/ui/input-group.tsx +156 -0
- package/frontend/src/components/ui/input.tsx +20 -0
- package/frontend/src/components/ui/label.tsx +20 -0
- package/frontend/src/components/ui/pagination.tsx +130 -0
- package/frontend/src/components/ui/popover.tsx +90 -0
- package/frontend/src/components/ui/progress.tsx +83 -0
- package/frontend/src/components/ui/radio-group.tsx +36 -0
- package/frontend/src/components/ui/scroll-area.tsx +54 -0
- package/frontend/src/components/ui/select.tsx +199 -0
- package/frontend/src/components/ui/separator.tsx +23 -0
- package/frontend/src/components/ui/sheet.tsx +138 -0
- package/frontend/src/components/ui/sidebar.tsx +723 -0
- package/frontend/src/components/ui/skeleton.tsx +13 -0
- package/frontend/src/components/ui/sonner.tsx +47 -0
- package/frontend/src/components/ui/switch.tsx +30 -0
- package/frontend/src/components/ui/table.tsx +114 -0
- package/frontend/src/components/ui/tabs.tsx +82 -0
- package/frontend/src/components/ui/textarea.tsx +18 -0
- package/frontend/src/components/ui/toggle-group.tsx +89 -0
- package/frontend/src/components/ui/toggle.tsx +43 -0
- package/frontend/src/components/ui/tooltip.tsx +64 -0
- package/frontend/src/hooks/use-mobile.ts +19 -0
- package/frontend/src/i18n/index.ts +20 -0
- package/frontend/src/i18n/locales/en.json +786 -0
- package/frontend/src/i18n/locales/tr.json +786 -0
- package/frontend/src/index.css +134 -0
- package/frontend/src/layouts/admin-layout.tsx +111 -0
- package/frontend/src/layouts/app-header.tsx +130 -0
- package/frontend/src/layouts/app-layout.tsx +19 -0
- package/frontend/src/layouts/app-sidebar.tsx +212 -0
- package/frontend/src/layouts/auth-guard.tsx +31 -0
- package/frontend/src/layouts/mobile-sidebar.tsx +120 -0
- package/frontend/src/lib/api.ts +68 -0
- package/frontend/src/lib/hooks/use-appointments.ts +224 -0
- package/frontend/src/lib/hooks/use-availability-blocks.ts +83 -0
- package/frontend/src/lib/hooks/use-chats.ts +236 -0
- package/frontend/src/lib/hooks/use-clinic-detail.ts +171 -0
- package/frontend/src/lib/hooks/use-clinics.ts +37 -0
- package/frontend/src/lib/hooks/use-doctor-calendar.ts +80 -0
- package/frontend/src/lib/hooks/use-examinations.ts +89 -0
- package/frontend/src/lib/hooks/use-medical-history.ts +52 -0
- package/frontend/src/lib/hooks/use-patients.ts +296 -0
- package/frontend/src/lib/hooks/use-photo-sets.ts +118 -0
- package/frontend/src/lib/hooks/use-treatment-plans.ts +152 -0
- package/frontend/src/lib/hooks/use-treatments.ts +125 -0
- package/frontend/src/lib/hooks/use-users.ts +172 -0
- package/frontend/src/lib/utils.ts +6 -0
- package/frontend/src/main.tsx +17 -0
- package/frontend/src/pages/admin/agents/detail.tsx +774 -0
- package/frontend/src/pages/admin/agents/import.tsx +280 -0
- package/frontend/src/pages/admin/agents/index.tsx +245 -0
- package/frontend/src/pages/admin/ai-playground.tsx +543 -0
- package/frontend/src/pages/appointments/create-appointment-dialog.tsx +390 -0
- package/frontend/src/pages/appointments/index.tsx +860 -0
- package/frontend/src/pages/audit/index.tsx +24 -0
- package/frontend/src/pages/auth/login.tsx +194 -0
- package/frontend/src/pages/billing/index.tsx +24 -0
- package/frontend/src/pages/calendar/index.tsx +704 -0
- package/frontend/src/pages/calendar-connections/index.tsx +295 -0
- package/frontend/src/pages/calls/index.tsx +24 -0
- package/frontend/src/pages/campaigns/index.tsx +24 -0
- package/frontend/src/pages/chats/index.tsx +981 -0
- package/frontend/src/pages/clinics/index.tsx +224 -0
- package/frontend/src/pages/clinics/settings.tsx +412 -0
- package/frontend/src/pages/components-showcase.tsx +773 -0
- package/frontend/src/pages/connections/index.tsx +328 -0
- package/frontend/src/pages/dashboard.tsx +50 -0
- package/frontend/src/pages/leads/index.tsx +24 -0
- package/frontend/src/pages/my-schedule/index.tsx +496 -0
- package/frontend/src/pages/patients/create-patient-dialog.tsx +358 -0
- package/frontend/src/pages/patients/detail.tsx +1195 -0
- package/frontend/src/pages/patients/edit-patient-dialog.tsx +387 -0
- package/frontend/src/pages/patients/examinations-tab.tsx +460 -0
- package/frontend/src/pages/patients/index.tsx +381 -0
- package/frontend/src/pages/patients/medical-history-dialog.tsx +207 -0
- package/frontend/src/pages/patients/photo-sets-tab.tsx +616 -0
- package/frontend/src/pages/patients/timeline-tab.tsx +164 -0
- package/frontend/src/pages/patients/treatment-plans-tab.tsx +598 -0
- package/frontend/src/pages/phone-numbers/detail.tsx +427 -0
- package/frontend/src/pages/phone-numbers/index.tsx +455 -0
- package/frontend/src/pages/platform/index.tsx +454 -0
- package/frontend/src/pages/settings/index.tsx +126 -0
- package/frontend/src/pages/treatments/index.tsx +487 -0
- package/frontend/src/pages/users/doctor-profile.tsx +672 -0
- package/frontend/src/pages/users/edit.tsx +329 -0
- package/frontend/src/pages/users/index.tsx +407 -0
- package/frontend/src/pages/validation/index.tsx +24 -0
- package/frontend/src/stores/auth.store.ts +108 -0
- package/frontend/src/stores/ui.store.ts +41 -0
- package/frontend/tsconfig.app.json +32 -0
- package/frontend/tsconfig.json +13 -0
- package/frontend/tsconfig.node.json +26 -0
- package/frontend/vite.config.ts +29 -0
- package/nestjs/.dockerignore +5 -0
- package/nestjs/.env.docker +64 -0
- package/nestjs/.prettierrc +4 -0
- package/nestjs/Dockerfile +36 -0
- package/nestjs/README.md +98 -0
- package/nestjs/eslint.config.mjs +35 -0
- package/nestjs/nest-cli.json +8 -0
- package/nestjs/package-lock.json +13390 -0
- package/nestjs/package.json +114 -0
- package/nestjs/prisma/migrations/20260409161536_add_message_metadata_fields/migration.sql +1746 -0
- package/nestjs/prisma/migrations/20260410140436_add_agent_ai_fields/migration.sql +36 -0
- package/nestjs/prisma/migrations/20260410175519_add_agent_clinic_assignments/migration.sql +21 -0
- package/nestjs/prisma/migrations/20260412094344_make_agent_tenant_optional/migration.sql +2 -0
- package/nestjs/prisma/migrations/20260412110008_add_admin_chat_sessions/migration.sql +47 -0
- package/nestjs/prisma/migrations/migration_lock.toml +3 -0
- package/nestjs/prisma/schema.prisma +1843 -0
- package/nestjs/prisma/seed.ts +375 -0
- package/nestjs/prisma.config.ts +14 -0
- package/nestjs/src/admin/admin.controller.ts +27 -0
- package/nestjs/src/admin/admin.module.ts +16 -0
- package/nestjs/src/admin/admin.service.ts +91 -0
- package/nestjs/src/admin/ai-chat-session.service.ts +454 -0
- package/nestjs/src/admin/ai-test.controller.ts +191 -0
- package/nestjs/src/admin/superadmin.controller.ts +106 -0
- package/nestjs/src/agents/agents.controller.ts +262 -0
- package/nestjs/src/agents/agents.module.ts +13 -0
- package/nestjs/src/agents/agents.service.ts +733 -0
- package/nestjs/src/agents/dto/create-agent.dto.ts +99 -0
- package/nestjs/src/agents/dto/index.ts +2 -0
- package/nestjs/src/agents/dto/update-agent.dto.ts +148 -0
- package/nestjs/src/app.module.ts +115 -0
- package/nestjs/src/appointment-validation/appointment-validation.controller.ts +194 -0
- package/nestjs/src/appointment-validation/appointment-validation.module.ts +16 -0
- package/nestjs/src/appointment-validation/appointment-validation.service.ts +450 -0
- package/nestjs/src/appointment-validation/dto/create-batch.dto.ts +105 -0
- package/nestjs/src/appointment-validation/dto/index.ts +1 -0
- package/nestjs/src/appointment-validation/processors/validation-dispatch.processor.ts +26 -0
- package/nestjs/src/appointment-validation/processors/validation-sync.processor.ts +23 -0
- package/nestjs/src/appointments/appointments.controller.ts +268 -0
- package/nestjs/src/appointments/appointments.module.ts +13 -0
- package/nestjs/src/appointments/appointments.service.ts +773 -0
- package/nestjs/src/appointments/dto/create-appointment.dto.ts +72 -0
- package/nestjs/src/appointments/dto/index.ts +4 -0
- package/nestjs/src/appointments/dto/query-appointments.dto.ts +60 -0
- package/nestjs/src/appointments/dto/update-appointment.dto.ts +43 -0
- package/nestjs/src/appointments/dto/update-status.dto.ts +18 -0
- package/nestjs/src/appointments/processors/reminder.processor.ts +243 -0
- package/nestjs/src/audit/audit.controller.ts +84 -0
- package/nestjs/src/audit/audit.decorator.ts +20 -0
- package/nestjs/src/audit/audit.interceptor.ts +67 -0
- package/nestjs/src/audit/audit.interfaces.ts +15 -0
- package/nestjs/src/audit/audit.module.ts +12 -0
- package/nestjs/src/audit/audit.service.ts +177 -0
- package/nestjs/src/auth/auth.controller.ts +116 -0
- package/nestjs/src/auth/auth.module.ts +25 -0
- package/nestjs/src/auth/auth.service.ts +612 -0
- package/nestjs/src/auth/dto/change-password.dto.ts +13 -0
- package/nestjs/src/auth/dto/forgot-password.dto.ts +8 -0
- package/nestjs/src/auth/dto/index.ts +6 -0
- package/nestjs/src/auth/dto/login.dto.ts +13 -0
- package/nestjs/src/auth/dto/refresh.dto.ts +8 -0
- package/nestjs/src/auth/dto/register.dto.ts +28 -0
- package/nestjs/src/auth/dto/reset-password.dto.ts +13 -0
- package/nestjs/src/auth/permissions.config.ts +85 -0
- package/nestjs/src/availability-blocks/availability-blocks.controller.ts +83 -0
- package/nestjs/src/availability-blocks/availability-blocks.module.ts +10 -0
- package/nestjs/src/availability-blocks/availability-blocks.service.ts +202 -0
- package/nestjs/src/billing/billing.controller.ts +104 -0
- package/nestjs/src/billing/billing.module.ts +12 -0
- package/nestjs/src/billing/billing.service.ts +398 -0
- package/nestjs/src/billing/dto/index.ts +2 -0
- package/nestjs/src/billing/dto/query-billing.dto.ts +29 -0
- package/nestjs/src/billing/dto/record-payment.dto.ts +60 -0
- package/nestjs/src/billing/processors/alerts.processor.ts +216 -0
- package/nestjs/src/billing/processors/snapshot.processor.ts +181 -0
- package/nestjs/src/calls/calls.controller.ts +92 -0
- package/nestjs/src/calls/calls.module.ts +10 -0
- package/nestjs/src/calls/calls.service.ts +359 -0
- package/nestjs/src/calls/dto/index.ts +1 -0
- package/nestjs/src/calls/dto/query-calls.dto.ts +46 -0
- package/nestjs/src/clinics/clinics.controller.ts +226 -0
- package/nestjs/src/clinics/clinics.module.ts +11 -0
- package/nestjs/src/clinics/clinics.service.ts +203 -0
- package/nestjs/src/clinics/dto/create-clinic.dto.ts +40 -0
- package/nestjs/src/clinics/dto/create-meta-connection.dto.ts +44 -0
- package/nestjs/src/clinics/dto/index.ts +4 -0
- package/nestjs/src/clinics/dto/update-clinic.dto.ts +133 -0
- package/nestjs/src/clinics/dto/update-meta-connection.dto.ts +22 -0
- package/nestjs/src/clinics/meta-connections.service.ts +210 -0
- package/nestjs/src/common/decorators/accessible-clinics.decorator.ts +9 -0
- package/nestjs/src/common/decorators/current-user.decorator.ts +10 -0
- package/nestjs/src/common/decorators/features.decorator.ts +7 -0
- package/nestjs/src/common/decorators/index.ts +4 -0
- package/nestjs/src/common/decorators/permissions.decorator.ts +13 -0
- package/nestjs/src/common/gateways/events.gateway.ts +157 -0
- package/nestjs/src/common/guards/clinic-access.guard.ts +40 -0
- package/nestjs/src/common/guards/features.guard.ts +38 -0
- package/nestjs/src/common/guards/index.ts +5 -0
- package/nestjs/src/common/guards/jwt-auth.guard.ts +54 -0
- package/nestjs/src/common/guards/permissions.guard.ts +50 -0
- package/nestjs/src/common/guards/superadmin.guard.ts +35 -0
- package/nestjs/src/common/interceptors/request-context.interceptor.ts +32 -0
- package/nestjs/src/common/interceptors/superadmin-tenant.interceptor.ts +42 -0
- package/nestjs/src/common/interfaces/jwt-payload.interface.ts +11 -0
- package/nestjs/src/config/config.module.ts +13 -0
- package/nestjs/src/database/database.module.ts +9 -0
- package/nestjs/src/database/prisma.service.ts +20 -0
- package/nestjs/src/database/tenant-context.ts +27 -0
- package/nestjs/src/google-calendar/google-calendar.controller.ts +259 -0
- package/nestjs/src/google-calendar/google-calendar.module.ts +10 -0
- package/nestjs/src/google-calendar/google-calendar.service.ts +811 -0
- package/nestjs/src/integrations/ai/ai.interface.ts +74 -0
- package/nestjs/src/integrations/ai/ai.module.ts +11 -0
- package/nestjs/src/integrations/ai/ai.service.ts +148 -0
- package/nestjs/src/integrations/ai/providers/anthropic.provider.ts +146 -0
- package/nestjs/src/integrations/ai/providers/openai.provider.ts +158 -0
- package/nestjs/src/integrations/elevenlabs/elevenlabs.module.ts +8 -0
- package/nestjs/src/integrations/elevenlabs/elevenlabs.service.ts +226 -0
- package/nestjs/src/integrations/encryption/encryption.module.ts +9 -0
- package/nestjs/src/integrations/encryption/encryption.service.ts +31 -0
- package/nestjs/src/integrations/image/image.module.ts +9 -0
- package/nestjs/src/integrations/image/image.service.ts +61 -0
- package/nestjs/src/integrations/meta-business/meta-business.module.ts +10 -0
- package/nestjs/src/integrations/meta-business/meta-instagram.service.ts +94 -0
- package/nestjs/src/integrations/meta-business/meta-webhook.service.ts +52 -0
- package/nestjs/src/integrations/meta-business/meta-whatsapp.service.ts +254 -0
- package/nestjs/src/integrations/minio/minio.module.ts +9 -0
- package/nestjs/src/integrations/minio/minio.service.ts +88 -0
- package/nestjs/src/integrations/netgsm/netgsm.module.ts +8 -0
- package/nestjs/src/integrations/netgsm/netgsm.service.ts +17 -0
- package/nestjs/src/integrations/tektippay/tektippay.module.ts +8 -0
- package/nestjs/src/integrations/tektippay/tektippay.service.ts +23 -0
- package/nestjs/src/leads/dto/index.ts +2 -0
- package/nestjs/src/leads/dto/query-leads.dto.ts +50 -0
- package/nestjs/src/leads/dto/update-lead.dto.ts +26 -0
- package/nestjs/src/leads/leads.controller.ts +184 -0
- package/nestjs/src/leads/leads.module.ts +10 -0
- package/nestjs/src/leads/leads.service.ts +375 -0
- package/nestjs/src/logging/logging.controller.ts +82 -0
- package/nestjs/src/logging/logging.module.ts +11 -0
- package/nestjs/src/logging/logging.service.ts +180 -0
- package/nestjs/src/main.ts +86 -0
- package/nestjs/src/outbound-campaigns/dto/create-campaign.dto.ts +47 -0
- package/nestjs/src/outbound-campaigns/dto/index.ts +3 -0
- package/nestjs/src/outbound-campaigns/dto/update-campaign.dto.ts +31 -0
- package/nestjs/src/outbound-campaigns/dto/upload-entries.dto.ts +35 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.controller.ts +307 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.module.ts +13 -0
- package/nestjs/src/outbound-campaigns/outbound-campaigns.service.ts +471 -0
- package/nestjs/src/outbound-campaigns/processors/campaign-dispatch.processor.ts +366 -0
- package/nestjs/src/patients/documents.service.ts +231 -0
- package/nestjs/src/patients/dto/create-examination.dto.ts +34 -0
- package/nestjs/src/patients/dto/create-note.dto.ts +14 -0
- package/nestjs/src/patients/dto/create-patient.dto.ts +86 -0
- package/nestjs/src/patients/dto/create-photo-set.dto.ts +32 -0
- package/nestjs/src/patients/dto/index.ts +10 -0
- package/nestjs/src/patients/dto/update-examination.dto.ts +29 -0
- package/nestjs/src/patients/dto/update-medical-history.dto.ts +47 -0
- package/nestjs/src/patients/dto/update-patient.dto.ts +87 -0
- package/nestjs/src/patients/dto/update-photo-set.dto.ts +28 -0
- package/nestjs/src/patients/dto/upload-document.dto.ts +31 -0
- package/nestjs/src/patients/dto/upload-photo.dto.ts +27 -0
- package/nestjs/src/patients/examinations.service.ts +271 -0
- package/nestjs/src/patients/medical-history.service.ts +149 -0
- package/nestjs/src/patients/notes.service.ts +172 -0
- package/nestjs/src/patients/patients.controller.ts +485 -0
- package/nestjs/src/patients/patients.module.ts +22 -0
- package/nestjs/src/patients/patients.service.ts +412 -0
- package/nestjs/src/patients/photo-sets.service.ts +389 -0
- package/nestjs/src/phone-numbers/dto/create-phone-number.dto.ts +57 -0
- package/nestjs/src/phone-numbers/dto/index.ts +3 -0
- package/nestjs/src/phone-numbers/dto/update-phone-number.dto.ts +38 -0
- package/nestjs/src/phone-numbers/dto/update-schedule.dto.ts +39 -0
- package/nestjs/src/phone-numbers/phone-numbers.controller.ts +125 -0
- package/nestjs/src/phone-numbers/phone-numbers.module.ts +10 -0
- package/nestjs/src/phone-numbers/phone-numbers.service.ts +209 -0
- package/nestjs/src/plans/dto/create-plan.dto.ts +70 -0
- package/nestjs/src/plans/dto/index.ts +2 -0
- package/nestjs/src/plans/dto/update-plan.dto.ts +80 -0
- package/nestjs/src/plans/plans.controller.ts +50 -0
- package/nestjs/src/plans/plans.module.ts +10 -0
- package/nestjs/src/plans/plans.service.ts +115 -0
- package/nestjs/src/platform/dto/create-tenant.dto.ts +36 -0
- package/nestjs/src/platform/dto/index.ts +2 -0
- package/nestjs/src/platform/dto/update-tenant-platform.dto.ts +44 -0
- package/nestjs/src/platform/platform.controller.ts +79 -0
- package/nestjs/src/platform/platform.module.ts +10 -0
- package/nestjs/src/platform/platform.service.ts +301 -0
- package/nestjs/src/queue/queue.module.ts +56 -0
- package/nestjs/src/redis/redis.module.ts +20 -0
- package/nestjs/src/tenants/dto/index.ts +1 -0
- package/nestjs/src/tenants/dto/update-tenant.dto.ts +15 -0
- package/nestjs/src/tenants/tenants.controller.ts +45 -0
- package/nestjs/src/tenants/tenants.module.ts +10 -0
- package/nestjs/src/tenants/tenants.service.ts +41 -0
- package/nestjs/src/tools/adapters/elevenlabs-http.adapter.ts +51 -0
- package/nestjs/src/tools/adapters/elevenlabs-ws.adapter.ts +59 -0
- package/nestjs/src/tools/handlers/calendar.tools.ts +441 -0
- package/nestjs/src/tools/handlers/notification.tools.ts +34 -0
- package/nestjs/src/tools/handlers/operator.tools.ts +303 -0
- package/nestjs/src/tools/handlers/patient.tools.ts +242 -0
- package/nestjs/src/tools/handlers/payment.tools.ts +43 -0
- package/nestjs/src/tools/handlers/validation.tools.ts +152 -0
- package/nestjs/src/tools/tool-registry.service.ts +221 -0
- package/nestjs/src/tools/tool.decorator.ts +16 -0
- package/nestjs/src/tools/tool.interfaces.ts +26 -0
- package/nestjs/src/tools/tools.module.ts +50 -0
- package/nestjs/src/treatments/dto/create-plan-item.dto.ts +27 -0
- package/nestjs/src/treatments/dto/create-treatment-plan.dto.ts +69 -0
- package/nestjs/src/treatments/dto/create-treatment.dto.ts +59 -0
- package/nestjs/src/treatments/dto/index.ts +6 -0
- package/nestjs/src/treatments/dto/update-plan-item.dto.ts +23 -0
- package/nestjs/src/treatments/dto/update-treatment-plan.dto.ts +22 -0
- package/nestjs/src/treatments/dto/update-treatment.dto.ts +70 -0
- package/nestjs/src/treatments/treatment-plans.service.ts +362 -0
- package/nestjs/src/treatments/treatments.controller.ts +265 -0
- package/nestjs/src/treatments/treatments.module.ts +14 -0
- package/nestjs/src/treatments/treatments.service.ts +165 -0
- package/nestjs/src/users/doctor-profiles.service.ts +202 -0
- package/nestjs/src/users/dto/index.ts +4 -0
- package/nestjs/src/users/dto/invite-user.dto.ts +52 -0
- package/nestjs/src/users/dto/update-clinic-assignments.dto.ts +9 -0
- package/nestjs/src/users/dto/update-doctor-profile.dto.ts +49 -0
- package/nestjs/src/users/dto/update-user.dto.ts +41 -0
- package/nestjs/src/users/users.controller.ts +142 -0
- package/nestjs/src/users/users.module.ts +11 -0
- package/nestjs/src/users/users.service.ts +250 -0
- package/nestjs/src/webhooks/elevenlabs-tool.controller.ts +66 -0
- package/nestjs/src/webhooks/elevenlabs-webhook.controller.ts +60 -0
- package/nestjs/src/webhooks/meta-webhook.controller.ts +178 -0
- package/nestjs/src/webhooks/processors/elevenlabs-webhook.processor.ts +28 -0
- package/nestjs/src/webhooks/webhooks.module.ts +17 -0
- package/nestjs/src/whatsapp/chat-context.service.ts +281 -0
- package/nestjs/src/whatsapp/dto/add-blacklist.dto.ts +13 -0
- package/nestjs/src/whatsapp/dto/index.ts +2 -0
- package/nestjs/src/whatsapp/dto/send-message.dto.ts +14 -0
- package/nestjs/src/whatsapp/listeners/meta-message.listener.ts +579 -0
- package/nestjs/src/whatsapp/meta-auth.controller.ts +268 -0
- package/nestjs/src/whatsapp/meta-instagram-auth.controller.ts +244 -0
- package/nestjs/src/whatsapp/operator.service.ts +692 -0
- package/nestjs/src/whatsapp/processors/cost-sweep.processor.ts +130 -0
- package/nestjs/src/whatsapp/processors/grace.processor.ts +191 -0
- package/nestjs/src/whatsapp/processors/message-buffer.processor.ts +138 -0
- package/nestjs/src/whatsapp/processors/message-cleanup.processor.ts +148 -0
- package/nestjs/src/whatsapp/processors/meta-token-refresh.processor.ts +114 -0
- package/nestjs/src/whatsapp/processors/operator-expiry.processor.ts +105 -0
- package/nestjs/src/whatsapp/processors/profile-update.processor.ts +234 -0
- package/nestjs/src/whatsapp/processors/session-cleanup.processor.ts +178 -0
- package/nestjs/src/whatsapp/processors/session-labels.processor.ts +248 -0
- package/nestjs/src/whatsapp/whatsapp-agent.service.ts +2506 -0
- package/nestjs/src/whatsapp/whatsapp-recovery.service.ts +117 -0
- package/nestjs/src/whatsapp/whatsapp.controller.ts +398 -0
- package/nestjs/src/whatsapp/whatsapp.module.ts +51 -0
- package/nestjs/src/whatsapp/whatsapp.service.ts +592 -0
- package/nestjs/test/app.e2e-spec.ts +25 -0
- package/nestjs/test/jest-e2e.json +9 -0
- package/nestjs/tsconfig.build.json +4 -0
- package/nestjs/tsconfig.json +25 -0
- package/nginx.example.conf +18 -0
- package/package.json +47 -0
- package/scripts/addRecipient.ts +48 -0
- package/scripts/listRecipients.ts +31 -0
- package/scripts/migrate-agent-ref.ts +86 -0
- package/scripts/migrate-sessions.ts +183 -0
- package/scripts/promote.ts +27 -0
- package/scripts/retrigger.ts +84 -0
- package/scripts/seed.ts +435 -0
- package/scripts/testSend.ts +63 -0
- package/src/app.ts +85 -0
- package/src/config/agentPrompts.json +19 -0
- package/src/config/database.ts +21 -0
- package/src/config/env.ts +94 -0
- package/src/config/index.ts +86 -0
- package/src/controllers/webhook.controller.ts +150 -0
- package/src/errors/index.ts +9 -0
- package/src/hooks/webhookValidator.ts +55 -0
- package/src/index.ts +68 -0
- package/src/migrations/001_session-architecture.ts +138 -0
- package/src/migrations/002_agent-ref.ts +65 -0
- package/src/migrations/003_shift-schedule.ts +55 -0
- package/src/migrations/004_invert-shift-to-clinic-hours.ts +30 -0
- package/src/migrations/005_composite-baileys-chat-ids.ts +60 -0
- package/src/migrations/migration.model.ts +27 -0
- package/src/migrations/migration.routes.ts +40 -0
- package/src/migrations/migration.runner.ts +112 -0
- package/src/models/ConversationClaim.ts +17 -0
- package/src/models/ConversationEvaluation.ts +91 -0
- package/src/models/Summary.ts +77 -0
- package/src/models/Transcription.ts +68 -0
- package/src/models/WhatsAppRecipient.ts +37 -0
- package/src/modules/admin/admin.routes.ts +2385 -0
- package/src/modules/admin/elevenlabs-test-chat.routes.ts +193 -0
- package/src/modules/admin/whatsapp-test-chat.routes.ts +244 -0
- package/src/modules/agents/agent.model.ts +93 -0
- package/src/modules/agents/agent.routes.ts +65 -0
- package/src/modules/appointment-validation/appointment-validation.model.ts +163 -0
- package/src/modules/appointment-validation/appointment-validation.routes.ts +275 -0
- package/src/modules/appointment-validation/appointment-validation.service.ts +1028 -0
- package/src/modules/auth/auth.model.ts +42 -0
- package/src/modules/auth/auth.routes.ts +199 -0
- package/src/modules/auth/auth.service.ts +210 -0
- package/src/modules/auth/refresh-token.model.ts +26 -0
- package/src/modules/billing/billing-alert.model.ts +28 -0
- package/src/modules/billing/billing-period-snapshot.model.ts +68 -0
- package/src/modules/billing/billing.model.ts +42 -0
- package/src/modules/billing/billing.routes.ts +67 -0
- package/src/modules/billing/billing.service.ts +562 -0
- package/src/modules/billing/payment.model.ts +42 -0
- package/src/modules/calls/call.model.ts +102 -0
- package/src/modules/calls/call.routes.ts +118 -0
- package/src/modules/campaigns/campaign.model.ts +111 -0
- package/src/modules/campaigns/campaign.routes.ts +402 -0
- package/src/modules/campaigns/campaign.service.ts +99 -0
- package/src/modules/google-calendar/google-calendar.routes.ts +31 -0
- package/src/modules/inbound-call/inbound-call-config.model.ts +49 -0
- package/src/modules/inbound-call/inbound-call.routes.ts +289 -0
- package/src/modules/leads/lead.model.ts +40 -0
- package/src/modules/leads/lead.routes.ts +246 -0
- package/src/modules/logs/log.model.ts +27 -0
- package/src/modules/logs/log.routes.ts +102 -0
- package/src/modules/plans/plan.model.ts +45 -0
- package/src/modules/surveys/survey.model.ts +70 -0
- package/src/modules/surveys/survey.routes.ts +187 -0
- package/src/modules/tenants/tenant.model.ts +181 -0
- package/src/modules/tenants/tenant.routes.ts +78 -0
- package/src/modules/users/user.routes.ts +126 -0
- package/src/modules/webhooks/elevenlabs-tool.routes.ts +94 -0
- package/src/modules/webhooks/elevenlabs-tool.service.ts +491 -0
- package/src/modules/webhooks/elevenlabs.routes.ts +34 -0
- package/src/modules/webhooks/elevenlabs.service.ts +565 -0
- package/src/modules/webhooks/unipile.routes.ts +917 -0
- package/src/modules/whatsapp/appointment.model.ts +47 -0
- package/src/modules/whatsapp/operator-request.model.ts +58 -0
- package/src/modules/whatsapp/stt-usage.model.ts +30 -0
- package/src/modules/whatsapp/whatsapp-chat.model.ts +39 -0
- package/src/modules/whatsapp/whatsapp-contact-profile.model.ts +41 -0
- package/src/modules/whatsapp/whatsapp-message.model.ts +41 -0
- package/src/modules/whatsapp/whatsapp-session.model.ts +60 -0
- package/src/modules/whatsapp/whatsapp.routes.ts +1435 -0
- package/src/plugins/cors.ts +7 -0
- package/src/plugins/jwt.ts +18 -0
- package/src/plugins/rawBody.ts +12 -0
- package/src/plugins/rbac.ts +24 -0
- package/src/routes/admin.routes.ts +208 -0
- package/src/routes/health.routes.ts +12 -0
- package/src/routes/webhook.routes.ts +12 -0
- package/src/services/ai/base.ai.ts +132 -0
- package/src/services/ai/gemini.service.ts +41 -0
- package/src/services/ai/index.ts +24 -0
- package/src/services/ai/openai.service.ts +48 -0
- package/src/services/elevenlabs.service.ts +532 -0
- package/src/services/google-calendar.service.ts +656 -0
- package/src/services/inbound-call-schedule.service.ts +174 -0
- package/src/services/netgsm.service.ts +128 -0
- package/src/services/unipile.service.ts +200 -0
- package/src/services/whatsapp-agent.service.ts +2479 -0
- package/src/services/whatsapp.service.ts +245 -0
- package/src/templates/index.ts +71 -0
- package/src/templates/receptionist.ts +44 -0
- package/src/templates/survey.ts +44 -0
- package/src/types/index.ts +218 -0
- package/src/utils/logger.ts +83 -0
- package/tsconfig.json +19 -0
- package/web/.dockerignore +4 -0
- package/web/Dockerfile +18 -0
- package/web/README.md +73 -0
- package/web/components.json +23 -0
- package/web/eslint.config.js +23 -0
- package/web/index.html +14 -0
- package/web/nginx.conf +18 -0
- package/web/package-lock.json +10292 -0
- package/web/package.json +48 -0
- package/web/public/favicon.ico +0 -0
- package/web/public/vite.svg +1 -0
- package/web/src/App.tsx +191 -0
- package/web/src/assets/react.svg +1 -0
- package/web/src/components/Layout.tsx +261 -0
- package/web/src/components/LeadConversation.tsx +251 -0
- package/web/src/components/ProtectedRoute.tsx +20 -0
- package/web/src/components/conversation-review/CardAudioPlayer.tsx +200 -0
- package/web/src/components/conversation-review/CardEvaluation.tsx +351 -0
- package/web/src/components/conversation-review/CardMetadata.tsx +44 -0
- package/web/src/components/conversation-review/ConversationCard.tsx +95 -0
- package/web/src/components/conversation-review/ReviewContainer.tsx +120 -0
- package/web/src/components/conversation-review/ReviewFilters.tsx +88 -0
- package/web/src/components/empty-state.tsx +15 -0
- package/web/src/components/page-header.tsx +19 -0
- package/web/src/components/page-loader.tsx +32 -0
- package/web/src/components/pagination.tsx +38 -0
- package/web/src/components/stat-card.tsx +27 -0
- package/web/src/components/status-badge.tsx +125 -0
- package/web/src/components/ui/alert.tsx +66 -0
- package/web/src/components/ui/badge.tsx +48 -0
- package/web/src/components/ui/button.tsx +64 -0
- package/web/src/components/ui/card.tsx +92 -0
- package/web/src/components/ui/checkbox.tsx +32 -0
- package/web/src/components/ui/dialog.tsx +158 -0
- package/web/src/components/ui/dropdown-menu.tsx +255 -0
- package/web/src/components/ui/input.tsx +21 -0
- package/web/src/components/ui/label.tsx +22 -0
- package/web/src/components/ui/progress.tsx +29 -0
- package/web/src/components/ui/select.tsx +188 -0
- package/web/src/components/ui/separator.tsx +28 -0
- package/web/src/components/ui/sheet.tsx +123 -0
- package/web/src/components/ui/skeleton.tsx +13 -0
- package/web/src/components/ui/sonner.tsx +35 -0
- package/web/src/components/ui/table.tsx +116 -0
- package/web/src/components/ui/tabs.tsx +89 -0
- package/web/src/components/ui/textarea.tsx +18 -0
- package/web/src/components/ui/tooltip.tsx +57 -0
- package/web/src/components/whatsapp/ChatDetail.tsx +417 -0
- package/web/src/components/whatsapp/ChatHeader.tsx +78 -0
- package/web/src/components/whatsapp/ChatList.tsx +107 -0
- package/web/src/components/whatsapp/ChatListItem.tsx +60 -0
- package/web/src/components/whatsapp/MessageBubble.tsx +46 -0
- package/web/src/components/whatsapp/MessageInput.tsx +63 -0
- package/web/src/components/whatsapp/MessageStream.tsx +135 -0
- package/web/src/components/whatsapp/SessionDivider.tsx +65 -0
- package/web/src/components/whatsapp/SessionHistory.tsx +119 -0
- package/web/src/components/whatsapp/settings/AiSettingsTab.tsx +268 -0
- package/web/src/components/whatsapp/settings/CalendarTab.tsx +339 -0
- package/web/src/components/whatsapp/settings/ConnectionTab.tsx +236 -0
- package/web/src/components/whatsapp/settings/NotificationsTab.tsx +109 -0
- package/web/src/components/whatsapp/settings/OperatorTab.tsx +303 -0
- package/web/src/contexts/AuthContext.tsx +103 -0
- package/web/src/index.css +130 -0
- package/web/src/lib/api.ts +92 -0
- package/web/src/lib/utils.ts +6 -0
- package/web/src/main.tsx +10 -0
- package/web/src/pages/AppointmentDetail.tsx +206 -0
- package/web/src/pages/AppointmentValidation.tsx +157 -0
- package/web/src/pages/AppointmentValidationDetail.tsx +617 -0
- package/web/src/pages/AppointmentValidationNew.tsx +1005 -0
- package/web/src/pages/Appointments.tsx +283 -0
- package/web/src/pages/Billing.tsx +126 -0
- package/web/src/pages/CallDetail.tsx +293 -0
- package/web/src/pages/CallSettings.tsx +313 -0
- package/web/src/pages/Calls.tsx +188 -0
- package/web/src/pages/CampaignDetail.tsx +216 -0
- package/web/src/pages/CampaignNew.tsx +277 -0
- package/web/src/pages/CampaignResults.tsx +185 -0
- package/web/src/pages/Campaigns.tsx +171 -0
- package/web/src/pages/Dashboard.tsx +336 -0
- package/web/src/pages/InboundSchedule.tsx +246 -0
- package/web/src/pages/LeadDetail.tsx +183 -0
- package/web/src/pages/Leads.tsx +258 -0
- package/web/src/pages/Login.tsx +99 -0
- package/web/src/pages/Register.tsx +129 -0
- package/web/src/pages/Settings.tsx +133 -0
- package/web/src/pages/SurveyDetail.tsx +232 -0
- package/web/src/pages/SurveyPreview.tsx +179 -0
- package/web/src/pages/Surveys.tsx +207 -0
- package/web/src/pages/Users.tsx +199 -0
- package/web/src/pages/WhatsApp.tsx +147 -0
- package/web/src/pages/WhatsAppSettings.tsx +215 -0
- package/web/src/pages/admin/AdminBaileysMessageLog.tsx +331 -0
- package/web/src/pages/admin/AdminBaileysRawMessages.tsx +318 -0
- package/web/src/pages/admin/AdminConversationReview.tsx +116 -0
- package/web/src/pages/admin/AdminCredits.tsx +467 -0
- package/web/src/pages/admin/AdminElevenLabsAgentDetail.tsx +332 -0
- package/web/src/pages/admin/AdminElevenLabsAgents.tsx +164 -0
- package/web/src/pages/admin/AdminElevenLabsBatchCallDetail.tsx +214 -0
- package/web/src/pages/admin/AdminElevenLabsBatchCalls.tsx +293 -0
- package/web/src/pages/admin/AdminElevenLabsConversationDetail.tsx +230 -0
- package/web/src/pages/admin/AdminElevenLabsConversations.tsx +228 -0
- package/web/src/pages/admin/AdminElevenLabsInboundCalls.tsx +293 -0
- package/web/src/pages/admin/AdminElevenLabsPhoneNumbers.tsx +506 -0
- package/web/src/pages/admin/AdminElevenLabsTestChat.tsx +258 -0
- package/web/src/pages/admin/AdminElevenLabsWebhooks.tsx +289 -0
- package/web/src/pages/admin/AdminEvaluationDashboard.tsx +520 -0
- package/web/src/pages/admin/AdminLeads.tsx +339 -0
- package/web/src/pages/admin/AdminLogs.tsx +283 -0
- package/web/src/pages/admin/AdminMigrations.tsx +247 -0
- package/web/src/pages/admin/AdminPlans.tsx +313 -0
- package/web/src/pages/admin/AdminSystem.tsx +391 -0
- package/web/src/pages/admin/AdminTenantBillableItems.tsx +464 -0
- package/web/src/pages/admin/AdminTenantDetail.tsx +1317 -0
- package/web/src/pages/admin/AdminTenants.tsx +274 -0
- package/web/src/pages/admin/AdminWhatsApp.tsx +618 -0
- package/web/src/pages/admin/AdminWhatsAppTestChat.tsx +328 -0
- package/web/src/types/index.ts +242 -0
- package/web/tsconfig.app.json +32 -0
- package/web/tsconfig.json +13 -0
- package/web/tsconfig.node.json +26 -0
- package/web/vite.config.ts +23 -0
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
# Appointments
|
|
2
|
+
|
|
3
|
+
> Full appointment lifecycle: Google Calendar integration, booking, cancellation, reminders, and batch validation.
|
|
4
|
+
> Last updated: 2026-04-02
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Google Calendar Integration
|
|
9
|
+
|
|
10
|
+
Each clinic connects its own Google Calendar through an OAuth2 flow. The connection is stored in the `clinic_calendar_connections` table, one row per clinic.
|
|
11
|
+
|
|
12
|
+
### 1.1 Data Model: ClinicCalendarConnection
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
clinic_calendar_connections
|
|
16
|
+
calcn_id UUID PK
|
|
17
|
+
calcn_clinic_id UUID UNIQUE FK -> clinics
|
|
18
|
+
calcn_google_email String? -- Google account email
|
|
19
|
+
calcn_encrypted_refresh_token String? -- AES-256-GCM encrypted
|
|
20
|
+
calcn_calendar_id String? -- Selected Google Calendar ID
|
|
21
|
+
calcn_calendar_name String?
|
|
22
|
+
calcn_timezone String -- default "Europe/Istanbul"
|
|
23
|
+
calcn_appointment_duration Int -- default 30 (minutes)
|
|
24
|
+
calcn_working_hours JSONB -- [{day, start, end}]
|
|
25
|
+
calcn_blocked_dates JSONB -- [{date, reason}]
|
|
26
|
+
calcn_connected_at DateTime?
|
|
27
|
+
calcn_created_at DateTime
|
|
28
|
+
calcn_updated_at DateTime
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Key points:**
|
|
32
|
+
- One-to-one with `clinics` via `calcn_clinic_id UNIQUE`.
|
|
33
|
+
- `calcn_encrypted_refresh_token` uses AES-256-GCM encryption with the `GOOGLE_TOKEN_ENCRYPTION_KEY` environment variable. Never stored in plaintext.
|
|
34
|
+
- `calcn_working_hours` is a JSON array of per-day schedules, e.g. `[{day: 1, start: "09:00", end: "18:00"}, ...]` where `day` is 0=Sunday through 6=Saturday.
|
|
35
|
+
- `calcn_blocked_dates` stores holidays and closures, e.g. `[{date: "2026-04-23", reason: "Ulusal Egemenlik"}]`.
|
|
36
|
+
|
|
37
|
+
### 1.2 OAuth2 Connection Flow
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Admin clicks "Connect Google Calendar"
|
|
41
|
+
|
|
|
42
|
+
v
|
|
43
|
+
GET /whatsapp/google-calendar/auth-url
|
|
44
|
+
| Generate HMAC-signed state parameter: HMAC(clinicId + timestamp, GOOGLE_OAUTH_STATE_SECRET)
|
|
45
|
+
| Build Google OAuth URL with scopes: calendar.readonly, calendar.events
|
|
46
|
+
| Return URL to frontend
|
|
47
|
+
v
|
|
48
|
+
Browser redirects to Google consent screen
|
|
49
|
+
| User authorizes
|
|
50
|
+
v
|
|
51
|
+
Google redirects to GET /google-calendar/callback?code=...&state=...
|
|
52
|
+
| 1. Verify state HMAC signature (prevents CSRF)
|
|
53
|
+
| 2. Exchange authorization code for tokens (POST to Google token endpoint)
|
|
54
|
+
| 3. Extract refresh_token from response
|
|
55
|
+
| 4. Encrypt refresh_token with AES-256-GCM using GOOGLE_TOKEN_ENCRYPTION_KEY
|
|
56
|
+
| 5. Store in clinic_calendar_connections.calcn_encrypted_refresh_token
|
|
57
|
+
| 6. Set calcn_connected_at = now()
|
|
58
|
+
| 7. Redirect browser to FRONTEND_URL/settings?calendar=connected
|
|
59
|
+
v
|
|
60
|
+
Connection established
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Security:**
|
|
64
|
+
- The callback route is unauthenticated (browser redirect from Google) but protected by the HMAC-signed state parameter.
|
|
65
|
+
- Refresh tokens are encrypted at rest. The encryption key is a 32-byte hex string generated with `openssl rand -hex 32`.
|
|
66
|
+
|
|
67
|
+
### 1.3 Token Management
|
|
68
|
+
|
|
69
|
+
Google access tokens expire after ~1 hour. The system caches them in Redis and manages refresh with per-tenant mutexes to prevent race conditions.
|
|
70
|
+
|
|
71
|
+
**Redis keys:**
|
|
72
|
+
- `gcal:token:{clinicId}` -- cached access token, 50-minute TTL (shorter than Google's ~60min expiry)
|
|
73
|
+
- `gcal:mutex:{clinicId}` -- refresh mutex, 10-second TTL
|
|
74
|
+
|
|
75
|
+
**Access token resolution:**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Need access token for clinicId
|
|
79
|
+
|
|
|
80
|
+
v
|
|
81
|
+
Check Redis: gcal:token:{clinicId}
|
|
82
|
+
|
|
|
83
|
+
+-- Cache hit --> return cached token
|
|
84
|
+
|
|
|
85
|
+
+-- Cache miss
|
|
86
|
+
|
|
|
87
|
+
v
|
|
88
|
+
Acquire mutex: SET gcal:mutex:{clinicId} NX EX 10
|
|
89
|
+
|
|
|
90
|
+
+-- Mutex acquired
|
|
91
|
+
| |
|
|
92
|
+
| v
|
|
93
|
+
| Decrypt refresh_token from DB (AES-256-GCM)
|
|
94
|
+
| POST to Google token endpoint with refresh_token
|
|
95
|
+
| Receive new access_token
|
|
96
|
+
| Cache in Redis with 50min TTL
|
|
97
|
+
| Release mutex
|
|
98
|
+
| Return access_token
|
|
99
|
+
|
|
|
100
|
+
+-- Mutex not acquired (another request is refreshing)
|
|
101
|
+
|
|
|
102
|
+
v
|
|
103
|
+
Wait briefly, retry Redis read
|
|
104
|
+
Return cached token (now populated by other request)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 1.4 Disconnect Flow
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
POST /whatsapp/google-calendar/disconnect
|
|
111
|
+
|
|
|
112
|
+
v
|
|
113
|
+
1. Delete clinic_calendar_connections row (or null out token fields)
|
|
114
|
+
2. Delete Redis cache: gcal:token:{clinicId}
|
|
115
|
+
3. Revoke token at Google (POST https://oauth2.googleapis.com/revoke)
|
|
116
|
+
4. Remove any scheduled appointment reminder jobs from BullMQ
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 2. Availability Checking
|
|
122
|
+
|
|
123
|
+
When the AI calls `check_availability(date)`, the system computes available slots by layering three filters.
|
|
124
|
+
|
|
125
|
+
### 2.1 Slot Duration Resolution
|
|
126
|
+
|
|
127
|
+
When generating candidate slots, the system determines slot duration through a priority chain:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
treatment.duration_minutes (if a treatment is specified)
|
|
131
|
+
→ doctor.profile_appointment_duration (if a doctor is specified)
|
|
132
|
+
→ clinic.calcn_appointment_duration
|
|
133
|
+
→ tenant.tenant_default_appt_duration (30 min fallback)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This means a specific treatment's duration takes highest priority, followed by the doctor's default appointment duration, then the clinic default.
|
|
137
|
+
|
|
138
|
+
### 2.2 Slot Generation Algorithm
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Input: date (YYYY-MM-DD), clinicId, doctorId? (optional), treatmentId? (optional)
|
|
142
|
+
|
|
143
|
+
Step 0: Resolve slot duration
|
|
144
|
+
| Apply the priority chain from Section 2.1
|
|
145
|
+
|
|
|
146
|
+
|
|
147
|
+
Step 1: Blocked date check
|
|
148
|
+
| Look up date in calcn_blocked_dates
|
|
149
|
+
| If found --> return "Clinic closed: {reason}"
|
|
150
|
+
|
|
|
151
|
+
Step 2: Working hours lookup
|
|
152
|
+
| If doctorId is specified:
|
|
153
|
+
| Look up doctor's profile_working_hours for matching day-of-week
|
|
154
|
+
| If no entry for this day --> return "Doctor not available on this day"
|
|
155
|
+
| Else:
|
|
156
|
+
| Look up calcn_working_hours for matching day-of-week
|
|
157
|
+
| If no entry for this day --> return "Clinic closed on this day"
|
|
158
|
+
| Extract start_time and end_time (e.g., "09:00" to "18:00")
|
|
159
|
+
|
|
|
160
|
+
Step 3: Generate candidate slots
|
|
161
|
+
| From start_time to end_time, generate slots of resolved slot duration
|
|
162
|
+
| Example (30min): 09:00, 09:30, 10:00, ..., 17:30
|
|
163
|
+
|
|
|
164
|
+
Step 4: Google FreeBusy API call
|
|
165
|
+
| POST https://www.googleapis.com/calendar/v3/freeBusy
|
|
166
|
+
| Body: {
|
|
167
|
+
| timeMin: "{date}T{start}:00+03:00",
|
|
168
|
+
| timeMax: "{date}T{end}:00+03:00",
|
|
169
|
+
| items: [{ id: calcn_calendar_id }]
|
|
170
|
+
| }
|
|
171
|
+
| Returns array of busy intervals: [{start, end}, ...]
|
|
172
|
+
|
|
|
173
|
+
Step 5: Filter out busy slots
|
|
174
|
+
| For each candidate slot, check if it overlaps any busy interval
|
|
175
|
+
| Remove overlapping slots
|
|
176
|
+
|
|
|
177
|
+
Step 6: Return available slots
|
|
178
|
+
| Format as list of {start, end} ISO strings
|
|
179
|
+
| Include total count and date
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Edge cases:**
|
|
183
|
+
- Past dates are rejected before any calendar query.
|
|
184
|
+
- The current date filters out slots already in the past (now + buffer).
|
|
185
|
+
- Timezone handling uses `calcn_timezone` (default `Europe/Istanbul`) for all calculations.
|
|
186
|
+
- When a doctor is specified, their `profile_working_hours` are used instead of the clinic-level working hours. If the doctor has no working hours configured, the clinic defaults are used as fallback.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 3. Booking Flow
|
|
191
|
+
|
|
192
|
+
### 3.1 Sequence Diagram
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Patient (WhatsApp) AI Agent CalendarTools GoogleCalendarService DB / Events
|
|
196
|
+
| | | | |
|
|
197
|
+
| "Book me for 10am" | | | |
|
|
198
|
+
|---------------------->| | | |
|
|
199
|
+
| | | | |
|
|
200
|
+
| | check_availability | | |
|
|
201
|
+
| | (date) | | |
|
|
202
|
+
| |--------------------->| | |
|
|
203
|
+
| | | FreeBusy API | |
|
|
204
|
+
| | |------------------------->| |
|
|
205
|
+
| | | available slots | |
|
|
206
|
+
| | |<-------------------------| |
|
|
207
|
+
| | slots | | |
|
|
208
|
+
| |<---------------------| | |
|
|
209
|
+
| | | | |
|
|
210
|
+
| "10:00 works" | | | |
|
|
211
|
+
|---------------------->| | | |
|
|
212
|
+
| | | | |
|
|
213
|
+
| | reserve_slot | | |
|
|
214
|
+
| | (name, start, end) | | |
|
|
215
|
+
| |--------------------->| | |
|
|
216
|
+
| | | | |
|
|
217
|
+
| | | 1. Re-check FreeBusy | |
|
|
218
|
+
| | | (race protection) | |
|
|
219
|
+
| | |------------------------->| |
|
|
220
|
+
| | | still free | |
|
|
221
|
+
| | |<-------------------------| |
|
|
222
|
+
| | | | |
|
|
223
|
+
| | | 2. Create Google event | |
|
|
224
|
+
| | | (with extendedProps) | |
|
|
225
|
+
| | |------------------------->| |
|
|
226
|
+
| | | event_id | |
|
|
227
|
+
| | |<-------------------------| |
|
|
228
|
+
| | | | |
|
|
229
|
+
| | | 3. Emit appointment.created ------------------>|
|
|
230
|
+
| | | | |
|
|
231
|
+
| | | | 4. Create Appointment
|
|
232
|
+
| | | | 5. Send SMS (NetGSM)
|
|
233
|
+
| | | | 6. Schedule reminders
|
|
234
|
+
| | | | |
|
|
235
|
+
| | confirmation | | |
|
|
236
|
+
| |<---------------------| | |
|
|
237
|
+
| "Confirmed: 10:00" | | | |
|
|
238
|
+
|<----------------------| | | |
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3.2 Step-by-Step
|
|
242
|
+
|
|
243
|
+
1. **AI calls `check_availability(date)`** -- the CalendarTools handler runs the slot generation algorithm (Section 2.1) and returns available time slots.
|
|
244
|
+
|
|
245
|
+
2. **AI calls `reserve_slot(patient_name, iso_start, iso_end, notes)`** -- this triggers the booking sequence:
|
|
246
|
+
|
|
247
|
+
3. **Re-check FreeBusy (race protection)** -- between the availability check and the booking, another patient may have taken the slot. The service calls Google FreeBusy API again for the exact time window. If the slot is now busy, return an error to the AI so it can suggest alternatives.
|
|
248
|
+
|
|
249
|
+
4. **Create Google Calendar event** -- if the slot is still free, create an event via the Calendar API:
|
|
250
|
+
```
|
|
251
|
+
POST /calendar/v3/calendars/{calendarId}/events
|
|
252
|
+
{
|
|
253
|
+
summary: "{patient_name} - Randevu",
|
|
254
|
+
start: { dateTime: iso_start, timeZone: timezone },
|
|
255
|
+
end: { dateTime: iso_end, timeZone: timezone },
|
|
256
|
+
description: notes,
|
|
257
|
+
extendedProperties: {
|
|
258
|
+
private: {
|
|
259
|
+
source: "portal-asistan",
|
|
260
|
+
patient_phone: "+905551234567",
|
|
261
|
+
tenant_id: "uuid",
|
|
262
|
+
clinic_id: "uuid"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
The `extendedProperties.private.source = 'portal-asistan'` tag identifies events created by the system. The `patient_phone` property enables ownership verification for cancellation and listing.
|
|
268
|
+
|
|
269
|
+
5. **Create Appointment record in DB** -- via EventEmitter (`appointment.created` event), fire-and-forget:
|
|
270
|
+
```
|
|
271
|
+
appointments table:
|
|
272
|
+
appt_clinic_id = clinicId (required)
|
|
273
|
+
appt_patient_id = resolved from ToolContext.patientPhone → Patient lookup (required)
|
|
274
|
+
appt_doctor_id = resolved from doctor_name param (nullable)
|
|
275
|
+
appt_treatment_id = resolved from treatment context (nullable)
|
|
276
|
+
appt_google_event_id = Google event ID
|
|
277
|
+
appt_patient_name = from params or context (denormalized)
|
|
278
|
+
appt_patient_phone = from ToolContext (denormalized)
|
|
279
|
+
appt_start_time = iso_start
|
|
280
|
+
appt_end_time = iso_end
|
|
281
|
+
appt_type = 'appointment' (default for AI bookings)
|
|
282
|
+
appt_source = 'whatsapp' | 'voice_call'
|
|
283
|
+
appt_status = 'scheduled'
|
|
284
|
+
appt_chat_id = from channelMetadata (nullable)
|
|
285
|
+
appt_session_id = from channelMetadata (nullable)
|
|
286
|
+
appt_conversation_id = from channelMetadata (nullable)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
6. **SMS notification to clinic** -- NetGSM sends an SMS to the clinic's configured notification phones informing staff of the new booking.
|
|
290
|
+
|
|
291
|
+
7. **Schedule appointment reminders** -- BullMQ delayed jobs are created (see Section 5 below).
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 4. Cancellation and Rescheduling
|
|
296
|
+
|
|
297
|
+
### 4.1 Cancellation (`cancel_reservation`)
|
|
298
|
+
|
|
299
|
+
**Parameters:** `{event_id}`
|
|
300
|
+
|
|
301
|
+
**Flow:**
|
|
302
|
+
1. Look up the Google Calendar event by `event_id`.
|
|
303
|
+
2. **Ownership verification:** read `extendedProperties.private.patient_phone` from the event. Compare with `ctx.patientPhone`. If mismatch, return error -- patients can only cancel their own appointments.
|
|
304
|
+
3. Delete the Google Calendar event via `DELETE /calendar/v3/calendars/{calendarId}/events/{eventId}`.
|
|
305
|
+
4. Update the Appointment record: set `appt_status = 'cancelled'`, `appt_cancelled_at = now()`.
|
|
306
|
+
5. Send SMS notification to the clinic about the cancellation.
|
|
307
|
+
6. Remove scheduled reminder jobs from BullMQ (by job ID pattern `reminder:{appointmentId}:*`).
|
|
308
|
+
|
|
309
|
+
### 4.2 Rescheduling (`reschedule_reservation`)
|
|
310
|
+
|
|
311
|
+
**Parameters:** `{old_event_id, new_iso_start, new_iso_end, notes}`
|
|
312
|
+
|
|
313
|
+
**Flow:**
|
|
314
|
+
1. Verify ownership of `old_event_id` (same as cancellation).
|
|
315
|
+
2. Re-check FreeBusy for the new time window (race protection).
|
|
316
|
+
3. Update the Google Calendar event with new start/end times via `PATCH /calendar/v3/calendars/{calendarId}/events/{eventId}`.
|
|
317
|
+
4. Update the Appointment record with new times.
|
|
318
|
+
5. Send SMS notification about the reschedule.
|
|
319
|
+
6. Remove old reminder jobs, schedule new ones for the updated time.
|
|
320
|
+
|
|
321
|
+
### 4.3 Listing Reservations (`list_reservations`)
|
|
322
|
+
|
|
323
|
+
Returns all upcoming appointments for the current patient (matched by `patient_phone`). Queries Google Calendar Events API filtering by `extendedProperties.private.patient_phone` and `extendedProperties.private.source = 'portal-asistan'`, with `timeMin = now()`.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 5. Appointment Reminders (BullMQ)
|
|
328
|
+
|
|
329
|
+
Reminders are the primary defense against no-shows. They are configurable per clinic and managed entirely through BullMQ delayed jobs.
|
|
330
|
+
|
|
331
|
+
### 5.1 Configuration
|
|
332
|
+
|
|
333
|
+
Stored in the clinic settings JSONB under `google_calendar.reminders`:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
reminders: {
|
|
337
|
+
enabled: boolean; // default true
|
|
338
|
+
channels: ('whatsapp' | 'sms')[]; // default ['whatsapp']
|
|
339
|
+
schedule: Array<{
|
|
340
|
+
hours_before: number; // e.g., 24, 2
|
|
341
|
+
message_template: string; // "Merhaba {{patient_name}}, ..."
|
|
342
|
+
}>;
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Default schedule:** 24 hours before and 2 hours before the appointment.
|
|
347
|
+
|
|
348
|
+
### 5.2 Scheduling Reminders on Booking
|
|
349
|
+
|
|
350
|
+
When an appointment is created, the system schedules one BullMQ delayed job per reminder entry:
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
For each reminder in clinic.reminders.schedule:
|
|
354
|
+
delay = appointmentStartTime - (hours_before * 3600000) - Date.now()
|
|
355
|
+
if delay > 0:
|
|
356
|
+
queue.add('appointment:reminder', {
|
|
357
|
+
appointmentId, clinicId, tenantId,
|
|
358
|
+
patientPhone, patientName, startTime,
|
|
359
|
+
template: reminder.message_template,
|
|
360
|
+
channels: reminders.channels,
|
|
361
|
+
}, {
|
|
362
|
+
delay: delay,
|
|
363
|
+
jobId: "reminder:{appointmentId}:{hours_before}h",
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The `jobId` pattern ensures idempotency -- re-scheduling the same reminder replaces rather than duplicates.
|
|
368
|
+
|
|
369
|
+
### 5.3 Reminder Processor
|
|
370
|
+
|
|
371
|
+
When the delayed job fires, the processor:
|
|
372
|
+
|
|
373
|
+
1. Verify the appointment still exists and is not cancelled.
|
|
374
|
+
2. Render the message template (replace `{{patient_name}}`, `{{time}}`, `{{date}}`, `{{clinic_name}}`).
|
|
375
|
+
3. For each channel in `channels`:
|
|
376
|
+
- `whatsapp`: Send via BridgeService (Meta Business template message or Baileys direct message).
|
|
377
|
+
- `sms`: Send via NetGSM.
|
|
378
|
+
|
|
379
|
+
### 5.4 Job Lifecycle on Mutations
|
|
380
|
+
|
|
381
|
+
| Event | Action on Reminder Jobs |
|
|
382
|
+
|-------|------------------------|
|
|
383
|
+
| Appointment created | Schedule new jobs |
|
|
384
|
+
| Appointment cancelled | Remove all jobs with `jobId` matching `reminder:{appointmentId}:*` |
|
|
385
|
+
| Appointment rescheduled | Remove old jobs, schedule new jobs with updated times |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 6. Appointment Validation (Batch Outbound Calls)
|
|
390
|
+
|
|
391
|
+
Appointment validation is a purpose-built system for confirming existing appointments via outbound phone calls. Clinics upload a list of upcoming appointments, and the AI calls each patient to confirm, cancel, or reschedule.
|
|
392
|
+
|
|
393
|
+
### 6.1 Data Model
|
|
394
|
+
|
|
395
|
+
**appointment_validation_batches:**
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
appointment_validation_batches
|
|
399
|
+
batch_id UUID PK
|
|
400
|
+
batch_tenant_id UUID FK -> tenants
|
|
401
|
+
batch_clinic_id UUID? FK -> clinics
|
|
402
|
+
batch_created_by_id UUID FK -> users
|
|
403
|
+
batch_name String
|
|
404
|
+
batch_status enum: draft | submitted | in_progress | completed | cancelled
|
|
405
|
+
batch_el_batch_id String? -- ElevenLabs batch call ID
|
|
406
|
+
batch_agent_id UUID? FK -> agents
|
|
407
|
+
batch_phone_id UUID? FK -> phone_numbers (outbound phone)
|
|
408
|
+
batch_column_mapping JSONB -- {patient_name, patient_phone, appointment_date, ...}
|
|
409
|
+
batch_call_schedule JSONB -- {call_date, call_start_time, call_end_time, max_concurrent}
|
|
410
|
+
batch_stats JSONB -- {total, pending, calling, completed, ...}
|
|
411
|
+
batch_created_at DateTime
|
|
412
|
+
batch_updated_at DateTime
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**appointment_validation_entries:**
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
appointment_validation_entries
|
|
419
|
+
bentry_id UUID PK
|
|
420
|
+
bentry_batch_id UUID FK -> appointment_validation_batches (CASCADE)
|
|
421
|
+
bentry_row_index Int
|
|
422
|
+
bentry_external_id String
|
|
423
|
+
bentry_patient_name String
|
|
424
|
+
bentry_patient_phone String
|
|
425
|
+
bentry_appointment_date String
|
|
426
|
+
bentry_appointment_time String
|
|
427
|
+
bentry_doctor_name String
|
|
428
|
+
bentry_department String
|
|
429
|
+
bentry_notes String
|
|
430
|
+
bentry_raw_data JSONB -- Original row data
|
|
431
|
+
bentry_call_status enum: pending | calling | completed | failed | no_answer | voicemail
|
|
432
|
+
bentry_attempts Int default 0
|
|
433
|
+
bentry_conversation_id String? -- ElevenLabs conversation ID
|
|
434
|
+
bentry_validation_result enum: confirmed | cancelled | reschedule | unresolved (nullable)
|
|
435
|
+
bentry_reschedule_request String?
|
|
436
|
+
bentry_patient_message String?
|
|
437
|
+
bentry_call_duration_secs Int default 0
|
|
438
|
+
bentry_called_at DateTime?
|
|
439
|
+
bentry_result_at DateTime?
|
|
440
|
+
bentry_created_at DateTime
|
|
441
|
+
bentry_updated_at DateTime
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### 6.2 Validation Batch Flow
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
Clinic Admin Frontend API BullMQ ElevenLabs
|
|
448
|
+
| | | | |
|
|
449
|
+
| Upload CSV/Excel | | | |
|
|
450
|
+
|------------------------->| | | |
|
|
451
|
+
| | Parse file (xlsx) | | |
|
|
452
|
+
| | Map columns | | |
|
|
453
|
+
| | Review entries | | |
|
|
454
|
+
| | | | |
|
|
455
|
+
| Confirm | | | |
|
|
456
|
+
|------------------------->| | | |
|
|
457
|
+
| | POST /appointment- | | |
|
|
458
|
+
| | validation | | |
|
|
459
|
+
| |---------------------->| | |
|
|
460
|
+
| | | Create batch (draft) | |
|
|
461
|
+
| | | Create entries | |
|
|
462
|
+
| | batch created | | |
|
|
463
|
+
| |<----------------------| | |
|
|
464
|
+
| | | | |
|
|
465
|
+
| Submit batch | | | |
|
|
466
|
+
|------------------------->| | | |
|
|
467
|
+
| | POST /:id/submit | | |
|
|
468
|
+
| |---------------------->| | |
|
|
469
|
+
| | | 1. Resolve agent | |
|
|
470
|
+
| | | 2. Resolve phone | |
|
|
471
|
+
| | | 3. batch -> submitted | |
|
|
472
|
+
| | | | |
|
|
473
|
+
| | | Dispatch calls ------>| |
|
|
474
|
+
| | | | For each entry: |
|
|
475
|
+
| | | | Check time window |
|
|
476
|
+
| | | | sipTrunkOutbound --->|
|
|
477
|
+
| | | | | Call patient
|
|
478
|
+
| | | | |
|
|
479
|
+
| | | | | AI: "Can you confirm
|
|
480
|
+
| | | | | your appointment?"
|
|
481
|
+
| | | | |
|
|
482
|
+
| | | | | Patient: "Yes, confirmed"
|
|
483
|
+
| | | | |
|
|
484
|
+
| | | | | AI calls tool:
|
|
485
|
+
| | | | | confirm_appointment_
|
|
486
|
+
| | | | | validation({
|
|
487
|
+
| | | | | result: "confirmed"
|
|
488
|
+
| | | | | })
|
|
489
|
+
| | | | |
|
|
490
|
+
| | | Webhook: tool result | |
|
|
491
|
+
| | |<----------------------------------------------|
|
|
492
|
+
| | | Update entry: | |
|
|
493
|
+
| | | validation_result = | |
|
|
494
|
+
| | | confirmed | |
|
|
495
|
+
| | | | |
|
|
496
|
+
| | | Post-call webhook | |
|
|
497
|
+
| | |<----------------------------------------------|
|
|
498
|
+
| | | Update entry: | |
|
|
499
|
+
| | | call_status, | |
|
|
500
|
+
| | | duration, conv_id | |
|
|
501
|
+
| | | | |
|
|
502
|
+
| | | Background sync | |
|
|
503
|
+
| | | (every 60s) | |
|
|
504
|
+
| | |<---------------------->| |
|
|
505
|
+
| | | Poll getBatchCall() | |
|
|
506
|
+
| | | Sync conversation_ids | |
|
|
507
|
+
| | | Infer unresolved | |
|
|
508
|
+
| | | | |
|
|
509
|
+
| | | All entries done --> | |
|
|
510
|
+
| | | batch -> completed | |
|
|
511
|
+
| | | | |
|
|
512
|
+
| Export CSV | | | |
|
|
513
|
+
|------------------------->| | | |
|
|
514
|
+
| | GET /:id/export | | |
|
|
515
|
+
| |---------------------->| | |
|
|
516
|
+
| | CSV file | | |
|
|
517
|
+
| |<----------------------| | |
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### 6.3 Batch Lifecycle
|
|
521
|
+
|
|
522
|
+
| State | Description | Allowed Transitions |
|
|
523
|
+
|-------|-------------|-------------------|
|
|
524
|
+
| `draft` | Batch created, entries uploaded. Editable. | submitted, (delete) |
|
|
525
|
+
| `submitted` | Calls being dispatched. Not editable. | in_progress, cancelled |
|
|
526
|
+
| `in_progress` | Calls actively running. | completed, cancelled |
|
|
527
|
+
| `completed` | All entries processed. | -- |
|
|
528
|
+
| `cancelled` | Manually cancelled. Pending calls aborted. | -- |
|
|
529
|
+
|
|
530
|
+
### 6.4 Call Dispatch (BullMQ)
|
|
531
|
+
|
|
532
|
+
Queue: `appointment-validation:dispatch`, concurrency: 3.
|
|
533
|
+
|
|
534
|
+
For each entry in the batch:
|
|
535
|
+
1. Check if within the configured time window (`batch_call_schedule.call_start_time` to `call_end_time`). If outside, delay the job until the window opens.
|
|
536
|
+
2. Set `bentry_call_status = 'calling'`, `bentry_attempts += 1`.
|
|
537
|
+
3. Call `elevenlabsService.sipTrunkOutboundCall()` with the patient phone, agent ID, and dynamic variables (patient name, appointment date/time, doctor name).
|
|
538
|
+
4. On success, record `bentry_conversation_id`.
|
|
539
|
+
5. On failure, mark `bentry_call_status = 'failed'`. If `attempts < max_attempts`, requeue with delay.
|
|
540
|
+
|
|
541
|
+
### 6.5 During the Call: `confirm_appointment_validation` Tool
|
|
542
|
+
|
|
543
|
+
The AI agent has a webhook tool registered at `POST /webhooks/elevenlabs/tools/confirm_appointment_validation`. When the patient gives a response, the AI calls this tool with:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"result": "confirmed", // confirmed | cancelled | reschedule
|
|
548
|
+
"reschedule_request": "...", // Free text if rescheduling
|
|
549
|
+
"patient_message": "..." // Patient's exact words
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
The webhook handler:
|
|
554
|
+
1. Resolves the tenant from `agent_id` header.
|
|
555
|
+
2. Finds the entry by `conversation_id`.
|
|
556
|
+
3. Updates `bentry_validation_result`, `bentry_reschedule_request`, `bentry_patient_message`, `bentry_result_at`.
|
|
557
|
+
4. Recalculates `batch_stats`.
|
|
558
|
+
|
|
559
|
+
### 6.6 Background Sync
|
|
560
|
+
|
|
561
|
+
Queue: `appointment-validation:sync`, repeatable every 60 seconds.
|
|
562
|
+
|
|
563
|
+
For each batch in `submitted` or `in_progress` status:
|
|
564
|
+
1. Call `elevenlabsService.getBatchCall(batch_el_batch_id)` to get current call statuses.
|
|
565
|
+
2. For entries missing `conversation_id`, match by phone number from the batch call response.
|
|
566
|
+
3. For completed calls where the tool did not fire (patient hung up, call failed), infer `validation_result = 'unresolved'`.
|
|
567
|
+
4. Update entry statuses and durations.
|
|
568
|
+
5. If all entries are processed, transition batch to `completed`.
|
|
569
|
+
|
|
570
|
+
### 6.7 CSV Export
|
|
571
|
+
|
|
572
|
+
`GET /appointment-validation/:id/export` generates a CSV with columns:
|
|
573
|
+
|
|
574
|
+
```
|
|
575
|
+
Row, Patient Name, Patient Phone, Appointment Date, Appointment Time, Doctor,
|
|
576
|
+
Department, Call Status, Validation Result, Reschedule Request, Patient Message,
|
|
577
|
+
Call Duration, Called At, Result At
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### 6.8 Validation Outcomes
|
|
581
|
+
|
|
582
|
+
| Result | Meaning | Follow-up |
|
|
583
|
+
|--------|---------|-----------|
|
|
584
|
+
| `confirmed` | Patient confirmed attendance | No action needed |
|
|
585
|
+
| `cancelled` | Patient wants to cancel | Clinic should free the slot |
|
|
586
|
+
| `reschedule` | Patient wants a different time | `reschedule_request` contains details |
|
|
587
|
+
| `unresolved` | No clear answer or call failed | May need manual follow-up or retry |
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## 7. Data Model Summary
|
|
592
|
+
|
|
593
|
+
### appointments
|
|
594
|
+
|
|
595
|
+
```
|
|
596
|
+
appointments
|
|
597
|
+
appt_id UUID PK
|
|
598
|
+
appt_tenant_id UUID FK -> tenants
|
|
599
|
+
appt_clinic_id UUID FK -> clinics (REQUIRED)
|
|
600
|
+
appt_patient_id UUID FK -> patients (REQUIRED)
|
|
601
|
+
appt_doctor_id UUID? FK -> users (doctor performing the treatment)
|
|
602
|
+
appt_treatment_id UUID? FK -> treatments (what treatment this is for)
|
|
603
|
+
appt_treatment_plan_item_id UUID? FK -> treatment_plan_items (which plan item, if part of a plan)
|
|
604
|
+
appt_chat_id UUID? FK -> whatsapp_chats
|
|
605
|
+
appt_session_id UUID? FK -> whatsapp_sessions
|
|
606
|
+
appt_google_event_id String
|
|
607
|
+
appt_patient_name String
|
|
608
|
+
appt_patient_phone String
|
|
609
|
+
appt_start_time DateTime
|
|
610
|
+
appt_end_time DateTime
|
|
611
|
+
appt_duration_minutes Int
|
|
612
|
+
appt_type enum: appointment | walk_in | follow_up | consultation
|
|
613
|
+
appt_notes String
|
|
614
|
+
appt_status enum: scheduled | confirmed | in_progress | completed | cancelled | no_show
|
|
615
|
+
appt_source enum: whatsapp | voice_call | dashboard | walk_in
|
|
616
|
+
appt_conversation_id String? -- ElevenLabs conversation ID
|
|
617
|
+
appt_cancelled_at DateTime?
|
|
618
|
+
appt_created_at DateTime
|
|
619
|
+
appt_updated_at DateTime
|
|
620
|
+
|
|
621
|
+
Indexes:
|
|
622
|
+
(appt_tenant_id, appt_start_time)
|
|
623
|
+
(appt_clinic_id, appt_start_time)
|
|
624
|
+
(appt_tenant_id, appt_status)
|
|
625
|
+
(appt_tenant_id, appt_patient_phone)
|
|
626
|
+
(appt_patient_id, appt_start_time) -- patient appointment history
|
|
627
|
+
(appt_doctor_id, appt_start_time) -- doctor's schedule
|
|
628
|
+
Unique:
|
|
629
|
+
(appt_google_event_id, appt_tenant_id)
|
|
630
|
+
|
|
631
|
+
Relations:
|
|
632
|
+
belongs_to patients via appt_patient_id
|
|
633
|
+
belongs_to users (doctor) via appt_doctor_id
|
|
634
|
+
belongs_to treatments via appt_treatment_id
|
|
635
|
+
belongs_to treatment_plan_items via appt_treatment_plan_item_id
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Status workflow:**
|
|
639
|
+
|
|
640
|
+
| Status | Meaning | Transitions to |
|
|
641
|
+
|--------|---------|---------------|
|
|
642
|
+
| `scheduled` | Booked, upcoming | confirmed, cancelled, no_show |
|
|
643
|
+
| `confirmed` | Patient confirmed (via validation call or manual) | in_progress, cancelled, no_show |
|
|
644
|
+
| `in_progress` | Patient is currently being seen | completed |
|
|
645
|
+
| `completed` | Visit finished | -- |
|
|
646
|
+
| `cancelled` | Cancelled before visit | -- |
|
|
647
|
+
| `no_show` | Patient did not show up | -- |
|
|
648
|
+
|
|
649
|
+
**Source values:**
|
|
650
|
+
|
|
651
|
+
| Source | When used |
|
|
652
|
+
|--------|-----------|
|
|
653
|
+
| `whatsapp` | Booked via WhatsApp AI |
|
|
654
|
+
| `voice_call` | Booked during a phone call |
|
|
655
|
+
| `dashboard` | Manually booked by staff from the dashboard |
|
|
656
|
+
| `walk_in` | Walk-in patient registered at reception |
|
|
657
|
+
|
|
658
|
+
**Type values:**
|
|
659
|
+
|
|
660
|
+
| Type | Meaning |
|
|
661
|
+
|------|---------|
|
|
662
|
+
| `appointment` | Standard scheduled appointment |
|
|
663
|
+
| `walk_in` | Walk-in visit (registered at reception) |
|
|
664
|
+
| `follow_up` | Follow-up visit for a previous treatment |
|
|
665
|
+
| `consultation` | Initial consultation / evaluation |
|
|
666
|
+
|
|
667
|
+
**Design decisions:**
|
|
668
|
+
- `appt_clinic_id` is required (not nullable) because every appointment belongs to a specific clinic's calendar.
|
|
669
|
+
- `appt_patient_id` is required -- every appointment belongs to a patient. For AI-booked appointments, the patient is resolved from `ToolContext.patientPhone` via a Patient lookup (or auto-created).
|
|
670
|
+
- `appt_doctor_id` links to a user with the doctor role. Nullable because not all appointments require a specific doctor at booking time.
|
|
671
|
+
- `appt_treatment_id` links to the treatment catalog. Allows the system to use the treatment's `duration_minutes` for slot calculation.
|
|
672
|
+
- `appt_treatment_plan_item_id` links to a specific item in a patient's treatment plan, enabling plan-to-appointment tracking.
|
|
673
|
+
- `appt_chat_id` and `appt_session_id` are nullable because voice call and dashboard appointments have no WhatsApp chat context.
|
|
674
|
+
- `appt_google_event_id` combined with `appt_tenant_id` is unique to prevent duplicate bookings.
|
|
675
|
+
- `appt_patient_name` and `appt_patient_phone` are kept as denormalized fields for display and backward compatibility, even though the canonical source is now the `patients` table via `appt_patient_id`.
|
|
676
|
+
|
|
677
|
+
### BullMQ Queues
|
|
678
|
+
|
|
679
|
+
| Queue | Purpose | Concurrency |
|
|
680
|
+
|-------|---------|-------------|
|
|
681
|
+
| `appointment:reminder` | Send pre-appointment reminders | 5 |
|
|
682
|
+
| `appointment-validation:dispatch` | Dispatch batch validation calls | 3 |
|
|
683
|
+
| `appointment-validation:sync` | Sync statuses from ElevenLabs | 1 |
|
|
684
|
+
|
|
685
|
+
### API Endpoints
|
|
686
|
+
|
|
687
|
+
**Calendar management:**
|
|
688
|
+
|
|
689
|
+
| Method | Path | Permission | Description |
|
|
690
|
+
|--------|------|-----------|-------------|
|
|
691
|
+
| GET | `/whatsapp/google-calendar/auth-url` | `calendar:manage` | Get Google OAuth URL |
|
|
692
|
+
| POST | `/whatsapp/google-calendar/disconnect` | `calendar:manage` | Disconnect calendar |
|
|
693
|
+
| GET | `/whatsapp/google-calendar/calendars` | `calendar:read` | List available calendars |
|
|
694
|
+
| PUT | `/whatsapp/google-calendar/calendar` | `calendar:manage` | Select active calendar |
|
|
695
|
+
|
|
696
|
+
**Appointments:**
|
|
697
|
+
|
|
698
|
+
| Method | Path | Permission | Description |
|
|
699
|
+
|--------|------|-----------|-------------|
|
|
700
|
+
| GET | `/whatsapp/appointments` | `appointments:read` | List (paginated, filterable) |
|
|
701
|
+
| GET | `/whatsapp/appointments/:id` | `appointments:read` | Detail |
|
|
702
|
+
| GET | `/whatsapp/appointments/stats` | `appointments:read` | Aggregate statistics |
|
|
703
|
+
| POST | `/appointments` | `appointments:manage` | Create appointment manually |
|
|
704
|
+
| PUT | `/appointments/:id` | `appointments:manage` | Update appointment (reschedule, change doctor) |
|
|
705
|
+
| PUT | `/appointments/:id/status` | `appointments:manage` | Update status (confirm, start, complete, no-show) |
|
|
706
|
+
|
|
707
|
+
**Appointment validation:**
|
|
708
|
+
|
|
709
|
+
| Method | Path | Permission | Description |
|
|
710
|
+
|--------|------|-----------|-------------|
|
|
711
|
+
| GET | `/appointment-validation` | `validation:read` | List batches |
|
|
712
|
+
| POST | `/appointment-validation` | `validation:manage` | Create batch with entries |
|
|
713
|
+
| GET | `/appointment-validation/:id` | `validation:read` | Batch detail with stats |
|
|
714
|
+
| POST | `/appointment-validation/:id/submit` | `validation:manage` | Submit for calling |
|
|
715
|
+
| POST | `/appointment-validation/:id/cancel` | `validation:manage` | Cancel batch |
|
|
716
|
+
| POST | `/appointment-validation/:id/retry` | `validation:manage` | Retry failed entries |
|
|
717
|
+
| POST | `/appointment-validation/:id/entries/:entryId/retry` | `validation:manage` | Retry single entry |
|
|
718
|
+
| GET | `/appointment-validation/:id/export` | `validation:export` | CSV export |
|
|
719
|
+
| POST | `/appointment-validation/:id/sync` | `validation:manage` | Manual sync from ElevenLabs |
|
|
720
|
+
|
|
721
|
+
### Feature Flags
|
|
722
|
+
|
|
723
|
+
| Flag | Gates |
|
|
724
|
+
|------|-------|
|
|
725
|
+
| `google_calendar` | All calendar tools (`check_availability`, `reserve_slot`, `list_reservations`, `cancel_reservation`, `reschedule_reservation`) |
|
|
726
|
+
| `appointment_validation` | All `/appointment-validation/*` endpoints and `confirm_appointment_validation` tool |
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## 8. Doctor Profiles
|
|
731
|
+
|
|
732
|
+
Each user with the `doctor` role has a corresponding `DoctorProfile` record that defines their availability and calendar preferences.
|
|
733
|
+
|
|
734
|
+
### 8.1 Data Model
|
|
735
|
+
|
|
736
|
+
```prisma
|
|
737
|
+
model DoctorProfile {
|
|
738
|
+
profile_id String @id @default(uuid())
|
|
739
|
+
profile_user_id String @unique
|
|
740
|
+
profile_specialty String @default("")
|
|
741
|
+
profile_title String @default("") // "Dr.", "Prof. Dr."
|
|
742
|
+
profile_bio String @default("")
|
|
743
|
+
profile_working_hours Json @default("[]") // [{day, start, end}]
|
|
744
|
+
profile_appointment_duration Int @default(30)
|
|
745
|
+
profile_color String @default("") // Calendar display color
|
|
746
|
+
profile_is_accepting Boolean @default(true)
|
|
747
|
+
profile_created_at DateTime @default(now())
|
|
748
|
+
profile_updated_at DateTime @updatedAt
|
|
749
|
+
|
|
750
|
+
user User @relation(fields: [profile_user_id], references: [user_id], onDelete: Cascade)
|
|
751
|
+
|
|
752
|
+
@@map("doctor_profiles")
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Key points:**
|
|
757
|
+
- 1:1 with `users` (doctor role) via `profile_user_id UNIQUE`. Created automatically when a user with the doctor role is added.
|
|
758
|
+
- `profile_working_hours` defines when this doctor is available for appointments, using the same format as clinic working hours: `[{day: 1, start: "09:00", end: "18:00"}, ...]` where `day` is 0=Sunday through 6=Saturday.
|
|
759
|
+
- `profile_appointment_duration` is the default slot duration for this doctor (in minutes). Overrides the clinic default (`calcn_appointment_duration`) when this doctor is selected for a booking.
|
|
760
|
+
- `profile_color` is a hex color code for the calendar UI -- each doctor gets a different color for visual distinction in the schedule view.
|
|
761
|
+
- `profile_is_accepting` controls whether this doctor appears in available doctor lists. Set to `false` when the doctor is on leave or not taking new appointments.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## 9. Manual Booking from Dashboard
|
|
766
|
+
|
|
767
|
+
Staff can book, update, and manage appointments directly from the dashboard without going through the AI flow.
|
|
768
|
+
|
|
769
|
+
### 9.1 API Endpoints
|
|
770
|
+
|
|
771
|
+
| Method | Path | Permission | Description |
|
|
772
|
+
|--------|------|-----------|-------------|
|
|
773
|
+
| POST | `/appointments` | `appointments:manage` | Create appointment manually (patient, doctor, treatment, time) |
|
|
774
|
+
| PUT | `/appointments/:id` | `appointments:manage` | Update appointment (reschedule, change doctor, update status) |
|
|
775
|
+
| PUT | `/appointments/:id/status` | `appointments:manage` | Update status (confirm, start, complete, no-show) |
|
|
776
|
+
|
|
777
|
+
### 9.2 Manual Booking Flow
|
|
778
|
+
|
|
779
|
+
```
|
|
780
|
+
Staff member opens Appointments page
|
|
781
|
+
|
|
|
782
|
+
v
|
|
783
|
+
1. Select patient
|
|
784
|
+
| Search by name or phone number
|
|
785
|
+
| Or create a new patient inline
|
|
786
|
+
|
|
|
787
|
+
v
|
|
788
|
+
2. Select treatment (from catalog)
|
|
789
|
+
| Treatment selection determines slot duration (if treatment.duration_minutes is set)
|
|
790
|
+
|
|
|
791
|
+
v
|
|
792
|
+
3. Select doctor (optional)
|
|
793
|
+
| List shows doctors with profile_is_accepting = true
|
|
794
|
+
| Doctor selection narrows available slots to doctor's working hours
|
|
795
|
+
|
|
|
796
|
+
v
|
|
797
|
+
4. System shows available slots
|
|
798
|
+
| Uses the same availability algorithm as Section 2
|
|
799
|
+
| Considers doctor's profile_working_hours (if selected)
|
|
800
|
+
| Considers treatment duration (if selected)
|
|
801
|
+
| Checks Google Calendar FreeBusy for conflicts
|
|
802
|
+
|
|
|
803
|
+
v
|
|
804
|
+
5. Staff selects slot and confirms
|
|
805
|
+
|
|
|
806
|
+
v
|
|
807
|
+
6. System creates:
|
|
808
|
+
| a. Google Calendar event (with extendedProperties)
|
|
809
|
+
| b. Appointment record in DB (appt_source = 'dashboard')
|
|
810
|
+
| c. Schedules BullMQ reminder jobs
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### 9.3 Status Transitions (Manual)
|
|
814
|
+
|
|
815
|
+
Staff can manually transition appointment statuses via `PUT /appointments/:id/status`:
|
|
816
|
+
|
|
817
|
+
```
|
|
818
|
+
scheduled ──> confirmed ──> in_progress ──> completed
|
|
819
|
+
| |
|
|
820
|
+
| +──> cancelled
|
|
821
|
+
| +──> no_show
|
|
822
|
+
+──> cancelled
|
|
823
|
+
+──> no_show
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
The status update endpoint enforces valid transitions and records the transition timestamp.
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## 10. AI Client Tools
|
|
831
|
+
|
|
832
|
+
The appointment system exposes the following tools through the generalized tool registry (see `src/tools/handlers/calendar.tools.ts` and `src/tools/handlers/validation.tools.ts`):
|
|
833
|
+
|
|
834
|
+
| Tool | Parameters | Channels | Feature |
|
|
835
|
+
|------|-----------|----------|---------|
|
|
836
|
+
| `check_availability` | `{date, doctor_name?, treatment_name?}` | all | `google_calendar` |
|
|
837
|
+
| `reserve_slot` | `{patient_name?, iso_start, iso_end, notes?, doctor_name?}` | all | `google_calendar` |
|
|
838
|
+
| `list_reservations` | -- | all | `google_calendar` |
|
|
839
|
+
| `cancel_reservation` | `{event_id}` | all | `google_calendar` |
|
|
840
|
+
| `reschedule_reservation` | `{old_event_id, new_iso_start, new_iso_end, notes?}` | all | `google_calendar` |
|
|
841
|
+
| `confirm_appointment_validation` | `{result, reschedule_request?, patient_message?}` | voice_call | `appointment_validation` |
|
|
842
|
+
|
|
843
|
+
All tools receive a `ToolContext` with tenant, clinic, patient phone, and channel metadata. All results are logged in the `tool_execution_logs` table.
|
|
844
|
+
|
|
845
|
+
**Patient linking:** `reserve_slot` resolves the patient from `ToolContext.patientPhone` by looking up the `patients` table. If a matching patient exists, `appt_patient_id` is set. If no patient is found, one is auto-created from the available context (phone, name).
|
|
846
|
+
|
|
847
|
+
**Doctor filtering:** When `doctor_name` is provided to `check_availability`, the system looks up the matching `DoctorProfile` and uses the doctor's `profile_working_hours` and `profile_appointment_duration` for slot generation. When `doctor_name` is provided to `reserve_slot`, the created appointment is linked via `appt_doctor_id`.
|
|
848
|
+
|
|
849
|
+
**Treatment filtering:** When `treatment_name` is provided to `check_availability`, the system looks up the matching treatment and uses `treatment.duration_minutes` for slot calculation (highest priority in the duration chain).
|