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.
Files changed (969) hide show
  1. package/.claude/settings.local.json +27 -0
  2. package/.dockerignore +6 -0
  3. package/.env.example +43 -0
  4. package/BEFORE-PRODUCTION.md +32 -0
  5. package/Dockerfile +23 -0
  6. package/SYSTEM_OVERVIEW.md +942 -0
  7. package/SYSTEM_STATUS.md +332 -0
  8. package/backup_script/main.py +146 -0
  9. package/baileys/.dockerignore +7 -0
  10. package/baileys/Dockerfile +13 -0
  11. package/baileys/README.md +412 -0
  12. package/baileys/index.js +499 -0
  13. package/baileys/package-lock.json +2532 -0
  14. package/baileys/package.json +25 -0
  15. package/baileys/server.js +96 -0
  16. package/baileys/src/config.js +55 -0
  17. package/baileys/src/middleware/api-key.js +16 -0
  18. package/baileys/src/routes/accounts.js +51 -0
  19. package/baileys/src/routes/chats.js +103 -0
  20. package/baileys/src/routes/webhooks.js +34 -0
  21. package/baileys/src/services/account-store.js +259 -0
  22. package/baileys/src/services/webhook-dispatcher.js +161 -0
  23. package/baileys/src/services/worker-manager.js +597 -0
  24. package/baileys/src/utils/jid.js +79 -0
  25. package/baileys/src/utils/logger.js +16 -0
  26. package/baileys/src/utils/use-mongodb-auth-state.js +122 -0
  27. package/baileys/worker.js +721 -0
  28. package/dist/app.d.ts +1 -0
  29. package/dist/app.js +84 -0
  30. package/dist/app.js.map +1 -0
  31. package/dist/config/agentPrompts.json +19 -0
  32. package/dist/config/database.d.ts +1 -0
  33. package/dist/config/database.js +26 -0
  34. package/dist/config/database.js.map +1 -0
  35. package/dist/config/env.d.ts +41 -0
  36. package/dist/config/env.js +81 -0
  37. package/dist/config/env.js.map +1 -0
  38. package/dist/config/index.d.ts +3 -0
  39. package/dist/config/index.js +73 -0
  40. package/dist/config/index.js.map +1 -0
  41. package/dist/controllers/webhook.controller.d.ts +10 -0
  42. package/dist/controllers/webhook.controller.js +128 -0
  43. package/dist/controllers/webhook.controller.js.map +1 -0
  44. package/dist/errors/index.d.ts +4 -0
  45. package/dist/errors/index.js +13 -0
  46. package/dist/errors/index.js.map +1 -0
  47. package/dist/hooks/webhookValidator.d.ts +2 -0
  48. package/dist/hooks/webhookValidator.js +47 -0
  49. package/dist/hooks/webhookValidator.js.map +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +55 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/migrations/001_session-architecture.d.ts +3 -0
  54. package/dist/migrations/001_session-architecture.js +119 -0
  55. package/dist/migrations/001_session-architecture.js.map +1 -0
  56. package/dist/migrations/002_agent-ref.d.ts +3 -0
  57. package/dist/migrations/002_agent-ref.js +55 -0
  58. package/dist/migrations/002_agent-ref.js.map +1 -0
  59. package/dist/migrations/003_shift-schedule.d.ts +3 -0
  60. package/dist/migrations/003_shift-schedule.js +48 -0
  61. package/dist/migrations/003_shift-schedule.js.map +1 -0
  62. package/dist/migrations/004_invert-shift-to-clinic-hours.d.ts +3 -0
  63. package/dist/migrations/004_invert-shift-to-clinic-hours.js +27 -0
  64. package/dist/migrations/004_invert-shift-to-clinic-hours.js.map +1 -0
  65. package/dist/migrations/005_composite-baileys-chat-ids.d.ts +3 -0
  66. package/dist/migrations/005_composite-baileys-chat-ids.js +47 -0
  67. package/dist/migrations/005_composite-baileys-chat-ids.js.map +1 -0
  68. package/dist/migrations/migration.model.d.ts +18 -0
  69. package/dist/migrations/migration.model.js +45 -0
  70. package/dist/migrations/migration.model.js.map +1 -0
  71. package/dist/migrations/migration.routes.d.ts +2 -0
  72. package/dist/migrations/migration.routes.js +37 -0
  73. package/dist/migrations/migration.routes.js.map +1 -0
  74. package/dist/migrations/migration.runner.d.ts +19 -0
  75. package/dist/migrations/migration.runner.js +74 -0
  76. package/dist/migrations/migration.runner.js.map +1 -0
  77. package/dist/models/ConversationClaim.d.ts +13 -0
  78. package/dist/models/ConversationClaim.js +44 -0
  79. package/dist/models/ConversationClaim.js.map +1 -0
  80. package/dist/models/ConversationEvaluation.d.ts +23 -0
  81. package/dist/models/ConversationEvaluation.js +92 -0
  82. package/dist/models/ConversationEvaluation.js.map +1 -0
  83. package/dist/models/Summary.d.ts +37 -0
  84. package/dist/models/Summary.js +78 -0
  85. package/dist/models/Summary.js.map +1 -0
  86. package/dist/models/Transcription.d.ts +34 -0
  87. package/dist/models/Transcription.js +71 -0
  88. package/dist/models/Transcription.js.map +1 -0
  89. package/dist/models/WhatsAppRecipient.d.ts +23 -0
  90. package/dist/models/WhatsAppRecipient.js +53 -0
  91. package/dist/models/WhatsAppRecipient.js.map +1 -0
  92. package/dist/modules/admin/admin.routes.d.ts +2 -0
  93. package/dist/modules/admin/admin.routes.js +1966 -0
  94. package/dist/modules/admin/admin.routes.js.map +1 -0
  95. package/dist/modules/admin/elevenlabs-test-chat.routes.d.ts +2 -0
  96. package/dist/modules/admin/elevenlabs-test-chat.routes.js +158 -0
  97. package/dist/modules/admin/elevenlabs-test-chat.routes.js.map +1 -0
  98. package/dist/modules/admin/whatsapp-test-chat.routes.d.ts +2 -0
  99. package/dist/modules/admin/whatsapp-test-chat.routes.js +204 -0
  100. package/dist/modules/admin/whatsapp-test-chat.routes.js.map +1 -0
  101. package/dist/modules/agents/agent.model.d.ts +38 -0
  102. package/dist/modules/agents/agent.model.js +92 -0
  103. package/dist/modules/agents/agent.model.js.map +1 -0
  104. package/dist/modules/agents/agent.routes.d.ts +2 -0
  105. package/dist/modules/agents/agent.routes.js +61 -0
  106. package/dist/modules/agents/agent.routes.js.map +1 -0
  107. package/dist/modules/appointment-validation/appointment-validation.model.d.ts +76 -0
  108. package/dist/modules/appointment-validation/appointment-validation.model.js +118 -0
  109. package/dist/modules/appointment-validation/appointment-validation.model.js.map +1 -0
  110. package/dist/modules/appointment-validation/appointment-validation.routes.d.ts +2 -0
  111. package/dist/modules/appointment-validation/appointment-validation.routes.js +202 -0
  112. package/dist/modules/appointment-validation/appointment-validation.routes.js.map +1 -0
  113. package/dist/modules/appointment-validation/appointment-validation.service.d.ts +53 -0
  114. package/dist/modules/appointment-validation/appointment-validation.service.js +827 -0
  115. package/dist/modules/appointment-validation/appointment-validation.service.js.map +1 -0
  116. package/dist/modules/auth/auth.model.d.ts +17 -0
  117. package/dist/modules/auth/auth.model.js +64 -0
  118. package/dist/modules/auth/auth.model.js.map +1 -0
  119. package/dist/modules/auth/auth.routes.d.ts +2 -0
  120. package/dist/modules/auth/auth.routes.js +202 -0
  121. package/dist/modules/auth/auth.routes.js.map +1 -0
  122. package/dist/modules/auth/auth.service.d.ts +28 -0
  123. package/dist/modules/auth/auth.service.js +183 -0
  124. package/dist/modules/auth/auth.service.js.map +1 -0
  125. package/dist/modules/auth/refresh-token.model.d.ts +13 -0
  126. package/dist/modules/auth/refresh-token.model.js +52 -0
  127. package/dist/modules/auth/refresh-token.model.js.map +1 -0
  128. package/dist/modules/billing/billing-alert.model.d.ts +16 -0
  129. package/dist/modules/billing/billing-alert.model.js +47 -0
  130. package/dist/modules/billing/billing-alert.model.js.map +1 -0
  131. package/dist/modules/billing/billing-period-snapshot.model.d.ts +35 -0
  132. package/dist/modules/billing/billing-period-snapshot.model.js +68 -0
  133. package/dist/modules/billing/billing-period-snapshot.model.js.map +1 -0
  134. package/dist/modules/billing/billing.model.d.ts +18 -0
  135. package/dist/modules/billing/billing.model.js +62 -0
  136. package/dist/modules/billing/billing.model.js.map +1 -0
  137. package/dist/modules/billing/billing.routes.d.ts +2 -0
  138. package/dist/modules/billing/billing.routes.js +63 -0
  139. package/dist/modules/billing/billing.routes.js.map +1 -0
  140. package/dist/modules/billing/billing.service.d.ts +69 -0
  141. package/dist/modules/billing/billing.service.js +498 -0
  142. package/dist/modules/billing/billing.service.js.map +1 -0
  143. package/dist/modules/billing/payment.model.d.ts +24 -0
  144. package/dist/modules/billing/payment.model.js +57 -0
  145. package/dist/modules/billing/payment.model.js.map +1 -0
  146. package/dist/modules/calls/call.model.d.ts +41 -0
  147. package/dist/modules/calls/call.model.js +97 -0
  148. package/dist/modules/calls/call.model.js.map +1 -0
  149. package/dist/modules/calls/call.routes.d.ts +2 -0
  150. package/dist/modules/calls/call.routes.js +103 -0
  151. package/dist/modules/calls/call.routes.js.map +1 -0
  152. package/dist/modules/campaigns/campaign.model.d.ts +45 -0
  153. package/dist/modules/campaigns/campaign.model.js +98 -0
  154. package/dist/modules/campaigns/campaign.model.js.map +1 -0
  155. package/dist/modules/campaigns/campaign.routes.d.ts +2 -0
  156. package/dist/modules/campaigns/campaign.routes.js +323 -0
  157. package/dist/modules/campaigns/campaign.routes.js.map +1 -0
  158. package/dist/modules/campaigns/campaign.service.d.ts +11 -0
  159. package/dist/modules/campaigns/campaign.service.js +86 -0
  160. package/dist/modules/campaigns/campaign.service.js.map +1 -0
  161. package/dist/modules/google-calendar/google-calendar.routes.d.ts +2 -0
  162. package/dist/modules/google-calendar/google-calendar.routes.js +32 -0
  163. package/dist/modules/google-calendar/google-calendar.routes.js.map +1 -0
  164. package/dist/modules/inbound-call/inbound-call-config.model.d.ts +20 -0
  165. package/dist/modules/inbound-call/inbound-call-config.model.js +68 -0
  166. package/dist/modules/inbound-call/inbound-call-config.model.js.map +1 -0
  167. package/dist/modules/inbound-call/inbound-call.routes.d.ts +2 -0
  168. package/dist/modules/inbound-call/inbound-call.routes.js +243 -0
  169. package/dist/modules/inbound-call/inbound-call.routes.js.map +1 -0
  170. package/dist/modules/leads/lead.model.d.ts +24 -0
  171. package/dist/modules/leads/lead.model.js +54 -0
  172. package/dist/modules/leads/lead.model.js.map +1 -0
  173. package/dist/modules/leads/lead.routes.d.ts +2 -0
  174. package/dist/modules/leads/lead.routes.js +201 -0
  175. package/dist/modules/leads/lead.routes.js.map +1 -0
  176. package/dist/modules/plans/plan.model.d.ts +25 -0
  177. package/dist/modules/plans/plan.model.js +59 -0
  178. package/dist/modules/plans/plan.model.js.map +1 -0
  179. package/dist/modules/surveys/survey.model.d.ts +30 -0
  180. package/dist/modules/surveys/survey.model.js +75 -0
  181. package/dist/modules/surveys/survey.model.js.map +1 -0
  182. package/dist/modules/surveys/survey.routes.d.ts +2 -0
  183. package/dist/modules/surveys/survey.routes.js +153 -0
  184. package/dist/modules/surveys/survey.routes.js.map +1 -0
  185. package/dist/modules/tenants/tenant.model.d.ts +86 -0
  186. package/dist/modules/tenants/tenant.model.js +127 -0
  187. package/dist/modules/tenants/tenant.model.js.map +1 -0
  188. package/dist/modules/tenants/tenant.routes.d.ts +2 -0
  189. package/dist/modules/tenants/tenant.routes.js +65 -0
  190. package/dist/modules/tenants/tenant.routes.js.map +1 -0
  191. package/dist/modules/users/user.routes.d.ts +2 -0
  192. package/dist/modules/users/user.routes.js +106 -0
  193. package/dist/modules/users/user.routes.js.map +1 -0
  194. package/dist/modules/webhooks/elevenlabs-tool.routes.d.ts +20 -0
  195. package/dist/modules/webhooks/elevenlabs-tool.routes.js +85 -0
  196. package/dist/modules/webhooks/elevenlabs-tool.routes.js.map +1 -0
  197. package/dist/modules/webhooks/elevenlabs-tool.service.d.ts +11 -0
  198. package/dist/modules/webhooks/elevenlabs-tool.service.js +360 -0
  199. package/dist/modules/webhooks/elevenlabs-tool.service.js.map +1 -0
  200. package/dist/modules/webhooks/elevenlabs.routes.d.ts +2 -0
  201. package/dist/modules/webhooks/elevenlabs.routes.js +34 -0
  202. package/dist/modules/webhooks/elevenlabs.routes.js.map +1 -0
  203. package/dist/modules/webhooks/elevenlabs.service.d.ts +6 -0
  204. package/dist/modules/webhooks/elevenlabs.service.js +512 -0
  205. package/dist/modules/webhooks/elevenlabs.service.js.map +1 -0
  206. package/dist/modules/webhooks/unipile.routes.d.ts +2 -0
  207. package/dist/modules/webhooks/unipile.routes.js +780 -0
  208. package/dist/modules/webhooks/unipile.routes.js.map +1 -0
  209. package/dist/modules/whatsapp/appointment.model.d.ts +27 -0
  210. package/dist/modules/whatsapp/appointment.model.js +58 -0
  211. package/dist/modules/whatsapp/appointment.model.js.map +1 -0
  212. package/dist/modules/whatsapp/operator-request.model.d.ts +29 -0
  213. package/dist/modules/whatsapp/operator-request.model.js +65 -0
  214. package/dist/modules/whatsapp/operator-request.model.js.map +1 -0
  215. package/dist/modules/whatsapp/stt-usage.model.d.ts +16 -0
  216. package/dist/modules/whatsapp/stt-usage.model.js +53 -0
  217. package/dist/modules/whatsapp/stt-usage.model.js.map +1 -0
  218. package/dist/modules/whatsapp/whatsapp-chat.model.d.ts +23 -0
  219. package/dist/modules/whatsapp/whatsapp-chat.model.js +55 -0
  220. package/dist/modules/whatsapp/whatsapp-chat.model.js.map +1 -0
  221. package/dist/modules/whatsapp/whatsapp-contact-profile.model.d.ts +23 -0
  222. package/dist/modules/whatsapp/whatsapp-contact-profile.model.js +54 -0
  223. package/dist/modules/whatsapp/whatsapp-contact-profile.model.js.map +1 -0
  224. package/dist/modules/whatsapp/whatsapp-message.model.d.ts +30 -0
  225. package/dist/modules/whatsapp/whatsapp-message.model.js +52 -0
  226. package/dist/modules/whatsapp/whatsapp-message.model.js.map +1 -0
  227. package/dist/modules/whatsapp/whatsapp-session.model.d.ts +33 -0
  228. package/dist/modules/whatsapp/whatsapp-session.model.js +65 -0
  229. package/dist/modules/whatsapp/whatsapp-session.model.js.map +1 -0
  230. package/dist/modules/whatsapp/whatsapp.routes.d.ts +2 -0
  231. package/dist/modules/whatsapp/whatsapp.routes.js +1237 -0
  232. package/dist/modules/whatsapp/whatsapp.routes.js.map +1 -0
  233. package/dist/plugins/cors.d.ts +3 -0
  234. package/dist/plugins/cors.js +11 -0
  235. package/dist/plugins/cors.js.map +1 -0
  236. package/dist/plugins/jwt.d.ts +3 -0
  237. package/dist/plugins/jwt.js +22 -0
  238. package/dist/plugins/jwt.js.map +1 -0
  239. package/dist/plugins/rawBody.d.ts +3 -0
  240. package/dist/plugins/rawBody.js +16 -0
  241. package/dist/plugins/rawBody.js.map +1 -0
  242. package/dist/plugins/rbac.d.ts +5 -0
  243. package/dist/plugins/rbac.js +29 -0
  244. package/dist/plugins/rbac.js.map +1 -0
  245. package/dist/routes/admin.routes.d.ts +2 -0
  246. package/dist/routes/admin.routes.js +169 -0
  247. package/dist/routes/admin.routes.js.map +1 -0
  248. package/dist/routes/health.routes.d.ts +2 -0
  249. package/dist/routes/health.routes.js +16 -0
  250. package/dist/routes/health.routes.js.map +1 -0
  251. package/dist/routes/webhook.routes.d.ts +2 -0
  252. package/dist/routes/webhook.routes.js +17 -0
  253. package/dist/routes/webhook.routes.js.map +1 -0
  254. package/dist/services/ai/base.ai.d.ts +19 -0
  255. package/dist/services/ai/base.ai.js +120 -0
  256. package/dist/services/ai/base.ai.js.map +1 -0
  257. package/dist/services/ai/gemini.service.d.ts +11 -0
  258. package/dist/services/ai/gemini.service.js +43 -0
  259. package/dist/services/ai/gemini.service.js.map +1 -0
  260. package/dist/services/ai/index.d.ts +2 -0
  261. package/dist/services/ai/index.js +26 -0
  262. package/dist/services/ai/index.js.map +1 -0
  263. package/dist/services/ai/openai.service.d.ts +11 -0
  264. package/dist/services/ai/openai.service.js +50 -0
  265. package/dist/services/ai/openai.service.js.map +1 -0
  266. package/dist/services/elevenlabs.service.d.ts +52 -0
  267. package/dist/services/elevenlabs.service.js +447 -0
  268. package/dist/services/elevenlabs.service.js.map +1 -0
  269. package/dist/services/google-calendar.service.d.ts +60 -0
  270. package/dist/services/google-calendar.service.js +494 -0
  271. package/dist/services/google-calendar.service.js.map +1 -0
  272. package/dist/services/inbound-call-schedule.service.d.ts +11 -0
  273. package/dist/services/inbound-call-schedule.service.js +162 -0
  274. package/dist/services/inbound-call-schedule.service.js.map +1 -0
  275. package/dist/services/netgsm.service.d.ts +41 -0
  276. package/dist/services/netgsm.service.js +89 -0
  277. package/dist/services/netgsm.service.js.map +1 -0
  278. package/dist/services/unipile.service.d.ts +41 -0
  279. package/dist/services/unipile.service.js +149 -0
  280. package/dist/services/unipile.service.js.map +1 -0
  281. package/dist/services/whatsapp-agent.service.d.ts +139 -0
  282. package/dist/services/whatsapp-agent.service.js +2055 -0
  283. package/dist/services/whatsapp-agent.service.js.map +1 -0
  284. package/dist/services/whatsapp.service.d.ts +26 -0
  285. package/dist/services/whatsapp.service.js +206 -0
  286. package/dist/services/whatsapp.service.js.map +1 -0
  287. package/dist/templates/index.d.ts +39 -0
  288. package/dist/templates/index.js +35 -0
  289. package/dist/templates/index.js.map +1 -0
  290. package/dist/templates/receptionist.d.ts +2 -0
  291. package/dist/templates/receptionist.js +39 -0
  292. package/dist/templates/receptionist.js.map +1 -0
  293. package/dist/templates/survey.d.ts +2 -0
  294. package/dist/templates/survey.js +41 -0
  295. package/dist/templates/survey.js.map +1 -0
  296. package/dist/types/index.d.ts +173 -0
  297. package/dist/types/index.js +3 -0
  298. package/dist/types/index.js.map +1 -0
  299. package/dist/utils/logger.d.ts +3 -0
  300. package/dist/utils/logger.js +105 -0
  301. package/dist/utils/logger.js.map +1 -0
  302. package/docker-compose.nestjs.yml +89 -0
  303. package/docker-compose.yml +78 -0
  304. package/docs/AI_AGENT_ENHANCEMENT_PLAN.md +164 -0
  305. package/docs/API.md +1193 -0
  306. package/docs/API_ENDPOINTS.md +344 -0
  307. package/docs/ARCHITECTURE.md +305 -0
  308. package/docs/AUTH_API.md +252 -0
  309. package/docs/BILLING_SMS_ALERTS.md +94 -0
  310. package/docs/CHAT_ASSIGNMENT_SYSTEM.md +118 -0
  311. package/docs/CLIENT_TOOLS_AND_FEATURES.md +337 -0
  312. package/docs/ELEVENLABS_WEBHOOK_TOOLS.md +644 -0
  313. package/docs/FRONTEND_CHECKLIST.md +227 -0
  314. package/docs/IMPLEMENTATION_STATUS.md +470 -0
  315. package/docs/MIGRATION_GUIDE.md +96 -0
  316. package/docs/MISSINGS_REPORT.md +507 -0
  317. package/docs/NESTJS_MIGRATION_REFERENCE.md +5136 -0
  318. package/docs/PROJECT_DESCRIPTION.md +1038 -0
  319. package/docs/SCALING.md +148 -0
  320. package/docs/SESSION_SUMMARY_2026_03_17.md +135 -0
  321. package/docs/WHATSAPP_AGENT.md +1086 -0
  322. package/docs/architecture/00-SYSTEM-OVERVIEW.md +318 -0
  323. package/docs/architecture/01-DATABASE-SCHEMA.md +2564 -0
  324. package/docs/architecture/02-AUTHENTICATION.md +1040 -0
  325. package/docs/architecture/03-MULTI-CLINIC.md +742 -0
  326. package/docs/architecture/04-WHATSAPP-AGENT.md +608 -0
  327. package/docs/architecture/05-OPERATOR-WORKFLOW.md +444 -0
  328. package/docs/architecture/06-BAILEYS-MICROSERVICE.md +616 -0
  329. package/docs/architecture/07-APPOINTMENTS.md +849 -0
  330. package/docs/architecture/08-VOICE-CALLS.md +470 -0
  331. package/docs/architecture/09-OUTBOUND-CAMPAIGNS.md +542 -0
  332. package/docs/architecture/10-CLIENT-TOOLS.md +665 -0
  333. package/docs/architecture/11-BILLING.md +458 -0
  334. package/docs/architecture/12-SECURITY.md +216 -0
  335. package/docs/architecture/13-LOGGING-AUDIT.md +549 -0
  336. package/docs/architecture/14-META-BUSINESS-API.md +454 -0
  337. package/docs/architecture/15-AI-MODULE.md +479 -0
  338. package/docs/architecture/16-BACKGROUND-JOBS.md +469 -0
  339. package/docs/architecture/17-REALTIME-LIVECHAT.md +447 -0
  340. package/docs/architecture/18-FILE-STORAGE.md +410 -0
  341. package/docs/architecture/19-PATIENTS.md +1034 -0
  342. package/docs/architecture/20-TREATMENTS-AND-PLANS.md +774 -0
  343. package/docs/architecture/21-BEFORE-AFTER-PHOTOS.md +519 -0
  344. package/docs/database.md +456 -0
  345. package/docs/ornek-randevu-onay.csv +3 -0
  346. package/ecosystem.config.js +16 -0
  347. package/elevenlabs-convai-api-reference.md +1171 -0
  348. package/frontend/.dockerignore +2 -0
  349. package/frontend/Dockerfile +24 -0
  350. package/frontend/README.md +75 -0
  351. package/frontend/components.json +25 -0
  352. package/frontend/eslint.config.js +23 -0
  353. package/frontend/index.html +13 -0
  354. package/frontend/nginx.conf +37 -0
  355. package/frontend/package-lock.json +8709 -0
  356. package/frontend/package.json +71 -0
  357. package/frontend/public/favicon.svg +1 -0
  358. package/frontend/public/icons.svg +24 -0
  359. package/frontend/src/App.tsx +125 -0
  360. package/frontend/src/components/error-boundary.tsx +50 -0
  361. package/frontend/src/components/shared/activity-timeline.tsx +66 -0
  362. package/frontend/src/components/shared/appointment-calendar.css +80 -0
  363. package/frontend/src/components/shared/appointment-calendar.tsx +245 -0
  364. package/frontend/src/components/shared/chat-bubble.tsx +72 -0
  365. package/frontend/src/components/shared/combobox.tsx +119 -0
  366. package/frontend/src/components/shared/confirm-dialog.tsx +57 -0
  367. package/frontend/src/components/shared/data-table-pagination.tsx +97 -0
  368. package/frontend/src/components/shared/data-table-toolbar.tsx +39 -0
  369. package/frontend/src/components/shared/empty-state.tsx +27 -0
  370. package/frontend/src/components/shared/file-upload.tsx +140 -0
  371. package/frontend/src/components/shared/index.ts +20 -0
  372. package/frontend/src/components/shared/language-switcher.tsx +29 -0
  373. package/frontend/src/components/shared/notification-dropdown.tsx +115 -0
  374. package/frontend/src/components/shared/page-header.tsx +23 -0
  375. package/frontend/src/components/shared/stat-card.tsx +37 -0
  376. package/frontend/src/components/shared/status-badge.tsx +70 -0
  377. package/frontend/src/components/shared/status-dot.tsx +43 -0
  378. package/frontend/src/components/ui/alert-dialog.tsx +187 -0
  379. package/frontend/src/components/ui/alert.tsx +76 -0
  380. package/frontend/src/components/ui/avatar.tsx +109 -0
  381. package/frontend/src/components/ui/badge.tsx +52 -0
  382. package/frontend/src/components/ui/breadcrumb.tsx +125 -0
  383. package/frontend/src/components/ui/button.tsx +60 -0
  384. package/frontend/src/components/ui/calendar.tsx +219 -0
  385. package/frontend/src/components/ui/card.tsx +103 -0
  386. package/frontend/src/components/ui/chart.tsx +371 -0
  387. package/frontend/src/components/ui/checkbox.tsx +29 -0
  388. package/frontend/src/components/ui/collapsible.tsx +19 -0
  389. package/frontend/src/components/ui/command.tsx +194 -0
  390. package/frontend/src/components/ui/dialog.tsx +158 -0
  391. package/frontend/src/components/ui/dropdown-menu.tsx +268 -0
  392. package/frontend/src/components/ui/input-group.tsx +156 -0
  393. package/frontend/src/components/ui/input.tsx +20 -0
  394. package/frontend/src/components/ui/label.tsx +20 -0
  395. package/frontend/src/components/ui/pagination.tsx +130 -0
  396. package/frontend/src/components/ui/popover.tsx +90 -0
  397. package/frontend/src/components/ui/progress.tsx +83 -0
  398. package/frontend/src/components/ui/radio-group.tsx +36 -0
  399. package/frontend/src/components/ui/scroll-area.tsx +54 -0
  400. package/frontend/src/components/ui/select.tsx +199 -0
  401. package/frontend/src/components/ui/separator.tsx +23 -0
  402. package/frontend/src/components/ui/sheet.tsx +138 -0
  403. package/frontend/src/components/ui/sidebar.tsx +723 -0
  404. package/frontend/src/components/ui/skeleton.tsx +13 -0
  405. package/frontend/src/components/ui/sonner.tsx +47 -0
  406. package/frontend/src/components/ui/switch.tsx +30 -0
  407. package/frontend/src/components/ui/table.tsx +114 -0
  408. package/frontend/src/components/ui/tabs.tsx +82 -0
  409. package/frontend/src/components/ui/textarea.tsx +18 -0
  410. package/frontend/src/components/ui/toggle-group.tsx +89 -0
  411. package/frontend/src/components/ui/toggle.tsx +43 -0
  412. package/frontend/src/components/ui/tooltip.tsx +64 -0
  413. package/frontend/src/hooks/use-mobile.ts +19 -0
  414. package/frontend/src/i18n/index.ts +20 -0
  415. package/frontend/src/i18n/locales/en.json +786 -0
  416. package/frontend/src/i18n/locales/tr.json +786 -0
  417. package/frontend/src/index.css +134 -0
  418. package/frontend/src/layouts/admin-layout.tsx +111 -0
  419. package/frontend/src/layouts/app-header.tsx +130 -0
  420. package/frontend/src/layouts/app-layout.tsx +19 -0
  421. package/frontend/src/layouts/app-sidebar.tsx +212 -0
  422. package/frontend/src/layouts/auth-guard.tsx +31 -0
  423. package/frontend/src/layouts/mobile-sidebar.tsx +120 -0
  424. package/frontend/src/lib/api.ts +68 -0
  425. package/frontend/src/lib/hooks/use-appointments.ts +224 -0
  426. package/frontend/src/lib/hooks/use-availability-blocks.ts +83 -0
  427. package/frontend/src/lib/hooks/use-chats.ts +236 -0
  428. package/frontend/src/lib/hooks/use-clinic-detail.ts +171 -0
  429. package/frontend/src/lib/hooks/use-clinics.ts +37 -0
  430. package/frontend/src/lib/hooks/use-doctor-calendar.ts +80 -0
  431. package/frontend/src/lib/hooks/use-examinations.ts +89 -0
  432. package/frontend/src/lib/hooks/use-medical-history.ts +52 -0
  433. package/frontend/src/lib/hooks/use-patients.ts +296 -0
  434. package/frontend/src/lib/hooks/use-photo-sets.ts +118 -0
  435. package/frontend/src/lib/hooks/use-treatment-plans.ts +152 -0
  436. package/frontend/src/lib/hooks/use-treatments.ts +125 -0
  437. package/frontend/src/lib/hooks/use-users.ts +172 -0
  438. package/frontend/src/lib/utils.ts +6 -0
  439. package/frontend/src/main.tsx +17 -0
  440. package/frontend/src/pages/admin/agents/detail.tsx +774 -0
  441. package/frontend/src/pages/admin/agents/import.tsx +280 -0
  442. package/frontend/src/pages/admin/agents/index.tsx +245 -0
  443. package/frontend/src/pages/admin/ai-playground.tsx +543 -0
  444. package/frontend/src/pages/appointments/create-appointment-dialog.tsx +390 -0
  445. package/frontend/src/pages/appointments/index.tsx +860 -0
  446. package/frontend/src/pages/audit/index.tsx +24 -0
  447. package/frontend/src/pages/auth/login.tsx +194 -0
  448. package/frontend/src/pages/billing/index.tsx +24 -0
  449. package/frontend/src/pages/calendar/index.tsx +704 -0
  450. package/frontend/src/pages/calendar-connections/index.tsx +295 -0
  451. package/frontend/src/pages/calls/index.tsx +24 -0
  452. package/frontend/src/pages/campaigns/index.tsx +24 -0
  453. package/frontend/src/pages/chats/index.tsx +981 -0
  454. package/frontend/src/pages/clinics/index.tsx +224 -0
  455. package/frontend/src/pages/clinics/settings.tsx +412 -0
  456. package/frontend/src/pages/components-showcase.tsx +773 -0
  457. package/frontend/src/pages/connections/index.tsx +328 -0
  458. package/frontend/src/pages/dashboard.tsx +50 -0
  459. package/frontend/src/pages/leads/index.tsx +24 -0
  460. package/frontend/src/pages/my-schedule/index.tsx +496 -0
  461. package/frontend/src/pages/patients/create-patient-dialog.tsx +358 -0
  462. package/frontend/src/pages/patients/detail.tsx +1195 -0
  463. package/frontend/src/pages/patients/edit-patient-dialog.tsx +387 -0
  464. package/frontend/src/pages/patients/examinations-tab.tsx +460 -0
  465. package/frontend/src/pages/patients/index.tsx +381 -0
  466. package/frontend/src/pages/patients/medical-history-dialog.tsx +207 -0
  467. package/frontend/src/pages/patients/photo-sets-tab.tsx +616 -0
  468. package/frontend/src/pages/patients/timeline-tab.tsx +164 -0
  469. package/frontend/src/pages/patients/treatment-plans-tab.tsx +598 -0
  470. package/frontend/src/pages/phone-numbers/detail.tsx +427 -0
  471. package/frontend/src/pages/phone-numbers/index.tsx +455 -0
  472. package/frontend/src/pages/platform/index.tsx +454 -0
  473. package/frontend/src/pages/settings/index.tsx +126 -0
  474. package/frontend/src/pages/treatments/index.tsx +487 -0
  475. package/frontend/src/pages/users/doctor-profile.tsx +672 -0
  476. package/frontend/src/pages/users/edit.tsx +329 -0
  477. package/frontend/src/pages/users/index.tsx +407 -0
  478. package/frontend/src/pages/validation/index.tsx +24 -0
  479. package/frontend/src/stores/auth.store.ts +108 -0
  480. package/frontend/src/stores/ui.store.ts +41 -0
  481. package/frontend/tsconfig.app.json +32 -0
  482. package/frontend/tsconfig.json +13 -0
  483. package/frontend/tsconfig.node.json +26 -0
  484. package/frontend/vite.config.ts +29 -0
  485. package/nestjs/.dockerignore +5 -0
  486. package/nestjs/.env.docker +64 -0
  487. package/nestjs/.prettierrc +4 -0
  488. package/nestjs/Dockerfile +36 -0
  489. package/nestjs/README.md +98 -0
  490. package/nestjs/eslint.config.mjs +35 -0
  491. package/nestjs/nest-cli.json +8 -0
  492. package/nestjs/package-lock.json +13390 -0
  493. package/nestjs/package.json +114 -0
  494. package/nestjs/prisma/migrations/20260409161536_add_message_metadata_fields/migration.sql +1746 -0
  495. package/nestjs/prisma/migrations/20260410140436_add_agent_ai_fields/migration.sql +36 -0
  496. package/nestjs/prisma/migrations/20260410175519_add_agent_clinic_assignments/migration.sql +21 -0
  497. package/nestjs/prisma/migrations/20260412094344_make_agent_tenant_optional/migration.sql +2 -0
  498. package/nestjs/prisma/migrations/20260412110008_add_admin_chat_sessions/migration.sql +47 -0
  499. package/nestjs/prisma/migrations/migration_lock.toml +3 -0
  500. package/nestjs/prisma/schema.prisma +1843 -0
  501. package/nestjs/prisma/seed.ts +375 -0
  502. package/nestjs/prisma.config.ts +14 -0
  503. package/nestjs/src/admin/admin.controller.ts +27 -0
  504. package/nestjs/src/admin/admin.module.ts +16 -0
  505. package/nestjs/src/admin/admin.service.ts +91 -0
  506. package/nestjs/src/admin/ai-chat-session.service.ts +454 -0
  507. package/nestjs/src/admin/ai-test.controller.ts +191 -0
  508. package/nestjs/src/admin/superadmin.controller.ts +106 -0
  509. package/nestjs/src/agents/agents.controller.ts +262 -0
  510. package/nestjs/src/agents/agents.module.ts +13 -0
  511. package/nestjs/src/agents/agents.service.ts +733 -0
  512. package/nestjs/src/agents/dto/create-agent.dto.ts +99 -0
  513. package/nestjs/src/agents/dto/index.ts +2 -0
  514. package/nestjs/src/agents/dto/update-agent.dto.ts +148 -0
  515. package/nestjs/src/app.module.ts +115 -0
  516. package/nestjs/src/appointment-validation/appointment-validation.controller.ts +194 -0
  517. package/nestjs/src/appointment-validation/appointment-validation.module.ts +16 -0
  518. package/nestjs/src/appointment-validation/appointment-validation.service.ts +450 -0
  519. package/nestjs/src/appointment-validation/dto/create-batch.dto.ts +105 -0
  520. package/nestjs/src/appointment-validation/dto/index.ts +1 -0
  521. package/nestjs/src/appointment-validation/processors/validation-dispatch.processor.ts +26 -0
  522. package/nestjs/src/appointment-validation/processors/validation-sync.processor.ts +23 -0
  523. package/nestjs/src/appointments/appointments.controller.ts +268 -0
  524. package/nestjs/src/appointments/appointments.module.ts +13 -0
  525. package/nestjs/src/appointments/appointments.service.ts +773 -0
  526. package/nestjs/src/appointments/dto/create-appointment.dto.ts +72 -0
  527. package/nestjs/src/appointments/dto/index.ts +4 -0
  528. package/nestjs/src/appointments/dto/query-appointments.dto.ts +60 -0
  529. package/nestjs/src/appointments/dto/update-appointment.dto.ts +43 -0
  530. package/nestjs/src/appointments/dto/update-status.dto.ts +18 -0
  531. package/nestjs/src/appointments/processors/reminder.processor.ts +243 -0
  532. package/nestjs/src/audit/audit.controller.ts +84 -0
  533. package/nestjs/src/audit/audit.decorator.ts +20 -0
  534. package/nestjs/src/audit/audit.interceptor.ts +67 -0
  535. package/nestjs/src/audit/audit.interfaces.ts +15 -0
  536. package/nestjs/src/audit/audit.module.ts +12 -0
  537. package/nestjs/src/audit/audit.service.ts +177 -0
  538. package/nestjs/src/auth/auth.controller.ts +116 -0
  539. package/nestjs/src/auth/auth.module.ts +25 -0
  540. package/nestjs/src/auth/auth.service.ts +612 -0
  541. package/nestjs/src/auth/dto/change-password.dto.ts +13 -0
  542. package/nestjs/src/auth/dto/forgot-password.dto.ts +8 -0
  543. package/nestjs/src/auth/dto/index.ts +6 -0
  544. package/nestjs/src/auth/dto/login.dto.ts +13 -0
  545. package/nestjs/src/auth/dto/refresh.dto.ts +8 -0
  546. package/nestjs/src/auth/dto/register.dto.ts +28 -0
  547. package/nestjs/src/auth/dto/reset-password.dto.ts +13 -0
  548. package/nestjs/src/auth/permissions.config.ts +85 -0
  549. package/nestjs/src/availability-blocks/availability-blocks.controller.ts +83 -0
  550. package/nestjs/src/availability-blocks/availability-blocks.module.ts +10 -0
  551. package/nestjs/src/availability-blocks/availability-blocks.service.ts +202 -0
  552. package/nestjs/src/billing/billing.controller.ts +104 -0
  553. package/nestjs/src/billing/billing.module.ts +12 -0
  554. package/nestjs/src/billing/billing.service.ts +398 -0
  555. package/nestjs/src/billing/dto/index.ts +2 -0
  556. package/nestjs/src/billing/dto/query-billing.dto.ts +29 -0
  557. package/nestjs/src/billing/dto/record-payment.dto.ts +60 -0
  558. package/nestjs/src/billing/processors/alerts.processor.ts +216 -0
  559. package/nestjs/src/billing/processors/snapshot.processor.ts +181 -0
  560. package/nestjs/src/calls/calls.controller.ts +92 -0
  561. package/nestjs/src/calls/calls.module.ts +10 -0
  562. package/nestjs/src/calls/calls.service.ts +359 -0
  563. package/nestjs/src/calls/dto/index.ts +1 -0
  564. package/nestjs/src/calls/dto/query-calls.dto.ts +46 -0
  565. package/nestjs/src/clinics/clinics.controller.ts +226 -0
  566. package/nestjs/src/clinics/clinics.module.ts +11 -0
  567. package/nestjs/src/clinics/clinics.service.ts +203 -0
  568. package/nestjs/src/clinics/dto/create-clinic.dto.ts +40 -0
  569. package/nestjs/src/clinics/dto/create-meta-connection.dto.ts +44 -0
  570. package/nestjs/src/clinics/dto/index.ts +4 -0
  571. package/nestjs/src/clinics/dto/update-clinic.dto.ts +133 -0
  572. package/nestjs/src/clinics/dto/update-meta-connection.dto.ts +22 -0
  573. package/nestjs/src/clinics/meta-connections.service.ts +210 -0
  574. package/nestjs/src/common/decorators/accessible-clinics.decorator.ts +9 -0
  575. package/nestjs/src/common/decorators/current-user.decorator.ts +10 -0
  576. package/nestjs/src/common/decorators/features.decorator.ts +7 -0
  577. package/nestjs/src/common/decorators/index.ts +4 -0
  578. package/nestjs/src/common/decorators/permissions.decorator.ts +13 -0
  579. package/nestjs/src/common/gateways/events.gateway.ts +157 -0
  580. package/nestjs/src/common/guards/clinic-access.guard.ts +40 -0
  581. package/nestjs/src/common/guards/features.guard.ts +38 -0
  582. package/nestjs/src/common/guards/index.ts +5 -0
  583. package/nestjs/src/common/guards/jwt-auth.guard.ts +54 -0
  584. package/nestjs/src/common/guards/permissions.guard.ts +50 -0
  585. package/nestjs/src/common/guards/superadmin.guard.ts +35 -0
  586. package/nestjs/src/common/interceptors/request-context.interceptor.ts +32 -0
  587. package/nestjs/src/common/interceptors/superadmin-tenant.interceptor.ts +42 -0
  588. package/nestjs/src/common/interfaces/jwt-payload.interface.ts +11 -0
  589. package/nestjs/src/config/config.module.ts +13 -0
  590. package/nestjs/src/database/database.module.ts +9 -0
  591. package/nestjs/src/database/prisma.service.ts +20 -0
  592. package/nestjs/src/database/tenant-context.ts +27 -0
  593. package/nestjs/src/google-calendar/google-calendar.controller.ts +259 -0
  594. package/nestjs/src/google-calendar/google-calendar.module.ts +10 -0
  595. package/nestjs/src/google-calendar/google-calendar.service.ts +811 -0
  596. package/nestjs/src/integrations/ai/ai.interface.ts +74 -0
  597. package/nestjs/src/integrations/ai/ai.module.ts +11 -0
  598. package/nestjs/src/integrations/ai/ai.service.ts +148 -0
  599. package/nestjs/src/integrations/ai/providers/anthropic.provider.ts +146 -0
  600. package/nestjs/src/integrations/ai/providers/openai.provider.ts +158 -0
  601. package/nestjs/src/integrations/elevenlabs/elevenlabs.module.ts +8 -0
  602. package/nestjs/src/integrations/elevenlabs/elevenlabs.service.ts +226 -0
  603. package/nestjs/src/integrations/encryption/encryption.module.ts +9 -0
  604. package/nestjs/src/integrations/encryption/encryption.service.ts +31 -0
  605. package/nestjs/src/integrations/image/image.module.ts +9 -0
  606. package/nestjs/src/integrations/image/image.service.ts +61 -0
  607. package/nestjs/src/integrations/meta-business/meta-business.module.ts +10 -0
  608. package/nestjs/src/integrations/meta-business/meta-instagram.service.ts +94 -0
  609. package/nestjs/src/integrations/meta-business/meta-webhook.service.ts +52 -0
  610. package/nestjs/src/integrations/meta-business/meta-whatsapp.service.ts +254 -0
  611. package/nestjs/src/integrations/minio/minio.module.ts +9 -0
  612. package/nestjs/src/integrations/minio/minio.service.ts +88 -0
  613. package/nestjs/src/integrations/netgsm/netgsm.module.ts +8 -0
  614. package/nestjs/src/integrations/netgsm/netgsm.service.ts +17 -0
  615. package/nestjs/src/integrations/tektippay/tektippay.module.ts +8 -0
  616. package/nestjs/src/integrations/tektippay/tektippay.service.ts +23 -0
  617. package/nestjs/src/leads/dto/index.ts +2 -0
  618. package/nestjs/src/leads/dto/query-leads.dto.ts +50 -0
  619. package/nestjs/src/leads/dto/update-lead.dto.ts +26 -0
  620. package/nestjs/src/leads/leads.controller.ts +184 -0
  621. package/nestjs/src/leads/leads.module.ts +10 -0
  622. package/nestjs/src/leads/leads.service.ts +375 -0
  623. package/nestjs/src/logging/logging.controller.ts +82 -0
  624. package/nestjs/src/logging/logging.module.ts +11 -0
  625. package/nestjs/src/logging/logging.service.ts +180 -0
  626. package/nestjs/src/main.ts +86 -0
  627. package/nestjs/src/outbound-campaigns/dto/create-campaign.dto.ts +47 -0
  628. package/nestjs/src/outbound-campaigns/dto/index.ts +3 -0
  629. package/nestjs/src/outbound-campaigns/dto/update-campaign.dto.ts +31 -0
  630. package/nestjs/src/outbound-campaigns/dto/upload-entries.dto.ts +35 -0
  631. package/nestjs/src/outbound-campaigns/outbound-campaigns.controller.ts +307 -0
  632. package/nestjs/src/outbound-campaigns/outbound-campaigns.module.ts +13 -0
  633. package/nestjs/src/outbound-campaigns/outbound-campaigns.service.ts +471 -0
  634. package/nestjs/src/outbound-campaigns/processors/campaign-dispatch.processor.ts +366 -0
  635. package/nestjs/src/patients/documents.service.ts +231 -0
  636. package/nestjs/src/patients/dto/create-examination.dto.ts +34 -0
  637. package/nestjs/src/patients/dto/create-note.dto.ts +14 -0
  638. package/nestjs/src/patients/dto/create-patient.dto.ts +86 -0
  639. package/nestjs/src/patients/dto/create-photo-set.dto.ts +32 -0
  640. package/nestjs/src/patients/dto/index.ts +10 -0
  641. package/nestjs/src/patients/dto/update-examination.dto.ts +29 -0
  642. package/nestjs/src/patients/dto/update-medical-history.dto.ts +47 -0
  643. package/nestjs/src/patients/dto/update-patient.dto.ts +87 -0
  644. package/nestjs/src/patients/dto/update-photo-set.dto.ts +28 -0
  645. package/nestjs/src/patients/dto/upload-document.dto.ts +31 -0
  646. package/nestjs/src/patients/dto/upload-photo.dto.ts +27 -0
  647. package/nestjs/src/patients/examinations.service.ts +271 -0
  648. package/nestjs/src/patients/medical-history.service.ts +149 -0
  649. package/nestjs/src/patients/notes.service.ts +172 -0
  650. package/nestjs/src/patients/patients.controller.ts +485 -0
  651. package/nestjs/src/patients/patients.module.ts +22 -0
  652. package/nestjs/src/patients/patients.service.ts +412 -0
  653. package/nestjs/src/patients/photo-sets.service.ts +389 -0
  654. package/nestjs/src/phone-numbers/dto/create-phone-number.dto.ts +57 -0
  655. package/nestjs/src/phone-numbers/dto/index.ts +3 -0
  656. package/nestjs/src/phone-numbers/dto/update-phone-number.dto.ts +38 -0
  657. package/nestjs/src/phone-numbers/dto/update-schedule.dto.ts +39 -0
  658. package/nestjs/src/phone-numbers/phone-numbers.controller.ts +125 -0
  659. package/nestjs/src/phone-numbers/phone-numbers.module.ts +10 -0
  660. package/nestjs/src/phone-numbers/phone-numbers.service.ts +209 -0
  661. package/nestjs/src/plans/dto/create-plan.dto.ts +70 -0
  662. package/nestjs/src/plans/dto/index.ts +2 -0
  663. package/nestjs/src/plans/dto/update-plan.dto.ts +80 -0
  664. package/nestjs/src/plans/plans.controller.ts +50 -0
  665. package/nestjs/src/plans/plans.module.ts +10 -0
  666. package/nestjs/src/plans/plans.service.ts +115 -0
  667. package/nestjs/src/platform/dto/create-tenant.dto.ts +36 -0
  668. package/nestjs/src/platform/dto/index.ts +2 -0
  669. package/nestjs/src/platform/dto/update-tenant-platform.dto.ts +44 -0
  670. package/nestjs/src/platform/platform.controller.ts +79 -0
  671. package/nestjs/src/platform/platform.module.ts +10 -0
  672. package/nestjs/src/platform/platform.service.ts +301 -0
  673. package/nestjs/src/queue/queue.module.ts +56 -0
  674. package/nestjs/src/redis/redis.module.ts +20 -0
  675. package/nestjs/src/tenants/dto/index.ts +1 -0
  676. package/nestjs/src/tenants/dto/update-tenant.dto.ts +15 -0
  677. package/nestjs/src/tenants/tenants.controller.ts +45 -0
  678. package/nestjs/src/tenants/tenants.module.ts +10 -0
  679. package/nestjs/src/tenants/tenants.service.ts +41 -0
  680. package/nestjs/src/tools/adapters/elevenlabs-http.adapter.ts +51 -0
  681. package/nestjs/src/tools/adapters/elevenlabs-ws.adapter.ts +59 -0
  682. package/nestjs/src/tools/handlers/calendar.tools.ts +441 -0
  683. package/nestjs/src/tools/handlers/notification.tools.ts +34 -0
  684. package/nestjs/src/tools/handlers/operator.tools.ts +303 -0
  685. package/nestjs/src/tools/handlers/patient.tools.ts +242 -0
  686. package/nestjs/src/tools/handlers/payment.tools.ts +43 -0
  687. package/nestjs/src/tools/handlers/validation.tools.ts +152 -0
  688. package/nestjs/src/tools/tool-registry.service.ts +221 -0
  689. package/nestjs/src/tools/tool.decorator.ts +16 -0
  690. package/nestjs/src/tools/tool.interfaces.ts +26 -0
  691. package/nestjs/src/tools/tools.module.ts +50 -0
  692. package/nestjs/src/treatments/dto/create-plan-item.dto.ts +27 -0
  693. package/nestjs/src/treatments/dto/create-treatment-plan.dto.ts +69 -0
  694. package/nestjs/src/treatments/dto/create-treatment.dto.ts +59 -0
  695. package/nestjs/src/treatments/dto/index.ts +6 -0
  696. package/nestjs/src/treatments/dto/update-plan-item.dto.ts +23 -0
  697. package/nestjs/src/treatments/dto/update-treatment-plan.dto.ts +22 -0
  698. package/nestjs/src/treatments/dto/update-treatment.dto.ts +70 -0
  699. package/nestjs/src/treatments/treatment-plans.service.ts +362 -0
  700. package/nestjs/src/treatments/treatments.controller.ts +265 -0
  701. package/nestjs/src/treatments/treatments.module.ts +14 -0
  702. package/nestjs/src/treatments/treatments.service.ts +165 -0
  703. package/nestjs/src/users/doctor-profiles.service.ts +202 -0
  704. package/nestjs/src/users/dto/index.ts +4 -0
  705. package/nestjs/src/users/dto/invite-user.dto.ts +52 -0
  706. package/nestjs/src/users/dto/update-clinic-assignments.dto.ts +9 -0
  707. package/nestjs/src/users/dto/update-doctor-profile.dto.ts +49 -0
  708. package/nestjs/src/users/dto/update-user.dto.ts +41 -0
  709. package/nestjs/src/users/users.controller.ts +142 -0
  710. package/nestjs/src/users/users.module.ts +11 -0
  711. package/nestjs/src/users/users.service.ts +250 -0
  712. package/nestjs/src/webhooks/elevenlabs-tool.controller.ts +66 -0
  713. package/nestjs/src/webhooks/elevenlabs-webhook.controller.ts +60 -0
  714. package/nestjs/src/webhooks/meta-webhook.controller.ts +178 -0
  715. package/nestjs/src/webhooks/processors/elevenlabs-webhook.processor.ts +28 -0
  716. package/nestjs/src/webhooks/webhooks.module.ts +17 -0
  717. package/nestjs/src/whatsapp/chat-context.service.ts +281 -0
  718. package/nestjs/src/whatsapp/dto/add-blacklist.dto.ts +13 -0
  719. package/nestjs/src/whatsapp/dto/index.ts +2 -0
  720. package/nestjs/src/whatsapp/dto/send-message.dto.ts +14 -0
  721. package/nestjs/src/whatsapp/listeners/meta-message.listener.ts +579 -0
  722. package/nestjs/src/whatsapp/meta-auth.controller.ts +268 -0
  723. package/nestjs/src/whatsapp/meta-instagram-auth.controller.ts +244 -0
  724. package/nestjs/src/whatsapp/operator.service.ts +692 -0
  725. package/nestjs/src/whatsapp/processors/cost-sweep.processor.ts +130 -0
  726. package/nestjs/src/whatsapp/processors/grace.processor.ts +191 -0
  727. package/nestjs/src/whatsapp/processors/message-buffer.processor.ts +138 -0
  728. package/nestjs/src/whatsapp/processors/message-cleanup.processor.ts +148 -0
  729. package/nestjs/src/whatsapp/processors/meta-token-refresh.processor.ts +114 -0
  730. package/nestjs/src/whatsapp/processors/operator-expiry.processor.ts +105 -0
  731. package/nestjs/src/whatsapp/processors/profile-update.processor.ts +234 -0
  732. package/nestjs/src/whatsapp/processors/session-cleanup.processor.ts +178 -0
  733. package/nestjs/src/whatsapp/processors/session-labels.processor.ts +248 -0
  734. package/nestjs/src/whatsapp/whatsapp-agent.service.ts +2506 -0
  735. package/nestjs/src/whatsapp/whatsapp-recovery.service.ts +117 -0
  736. package/nestjs/src/whatsapp/whatsapp.controller.ts +398 -0
  737. package/nestjs/src/whatsapp/whatsapp.module.ts +51 -0
  738. package/nestjs/src/whatsapp/whatsapp.service.ts +592 -0
  739. package/nestjs/test/app.e2e-spec.ts +25 -0
  740. package/nestjs/test/jest-e2e.json +9 -0
  741. package/nestjs/tsconfig.build.json +4 -0
  742. package/nestjs/tsconfig.json +25 -0
  743. package/nginx.example.conf +18 -0
  744. package/package.json +47 -0
  745. package/scripts/addRecipient.ts +48 -0
  746. package/scripts/listRecipients.ts +31 -0
  747. package/scripts/migrate-agent-ref.ts +86 -0
  748. package/scripts/migrate-sessions.ts +183 -0
  749. package/scripts/promote.ts +27 -0
  750. package/scripts/retrigger.ts +84 -0
  751. package/scripts/seed.ts +435 -0
  752. package/scripts/testSend.ts +63 -0
  753. package/src/app.ts +85 -0
  754. package/src/config/agentPrompts.json +19 -0
  755. package/src/config/database.ts +21 -0
  756. package/src/config/env.ts +94 -0
  757. package/src/config/index.ts +86 -0
  758. package/src/controllers/webhook.controller.ts +150 -0
  759. package/src/errors/index.ts +9 -0
  760. package/src/hooks/webhookValidator.ts +55 -0
  761. package/src/index.ts +68 -0
  762. package/src/migrations/001_session-architecture.ts +138 -0
  763. package/src/migrations/002_agent-ref.ts +65 -0
  764. package/src/migrations/003_shift-schedule.ts +55 -0
  765. package/src/migrations/004_invert-shift-to-clinic-hours.ts +30 -0
  766. package/src/migrations/005_composite-baileys-chat-ids.ts +60 -0
  767. package/src/migrations/migration.model.ts +27 -0
  768. package/src/migrations/migration.routes.ts +40 -0
  769. package/src/migrations/migration.runner.ts +112 -0
  770. package/src/models/ConversationClaim.ts +17 -0
  771. package/src/models/ConversationEvaluation.ts +91 -0
  772. package/src/models/Summary.ts +77 -0
  773. package/src/models/Transcription.ts +68 -0
  774. package/src/models/WhatsAppRecipient.ts +37 -0
  775. package/src/modules/admin/admin.routes.ts +2385 -0
  776. package/src/modules/admin/elevenlabs-test-chat.routes.ts +193 -0
  777. package/src/modules/admin/whatsapp-test-chat.routes.ts +244 -0
  778. package/src/modules/agents/agent.model.ts +93 -0
  779. package/src/modules/agents/agent.routes.ts +65 -0
  780. package/src/modules/appointment-validation/appointment-validation.model.ts +163 -0
  781. package/src/modules/appointment-validation/appointment-validation.routes.ts +275 -0
  782. package/src/modules/appointment-validation/appointment-validation.service.ts +1028 -0
  783. package/src/modules/auth/auth.model.ts +42 -0
  784. package/src/modules/auth/auth.routes.ts +199 -0
  785. package/src/modules/auth/auth.service.ts +210 -0
  786. package/src/modules/auth/refresh-token.model.ts +26 -0
  787. package/src/modules/billing/billing-alert.model.ts +28 -0
  788. package/src/modules/billing/billing-period-snapshot.model.ts +68 -0
  789. package/src/modules/billing/billing.model.ts +42 -0
  790. package/src/modules/billing/billing.routes.ts +67 -0
  791. package/src/modules/billing/billing.service.ts +562 -0
  792. package/src/modules/billing/payment.model.ts +42 -0
  793. package/src/modules/calls/call.model.ts +102 -0
  794. package/src/modules/calls/call.routes.ts +118 -0
  795. package/src/modules/campaigns/campaign.model.ts +111 -0
  796. package/src/modules/campaigns/campaign.routes.ts +402 -0
  797. package/src/modules/campaigns/campaign.service.ts +99 -0
  798. package/src/modules/google-calendar/google-calendar.routes.ts +31 -0
  799. package/src/modules/inbound-call/inbound-call-config.model.ts +49 -0
  800. package/src/modules/inbound-call/inbound-call.routes.ts +289 -0
  801. package/src/modules/leads/lead.model.ts +40 -0
  802. package/src/modules/leads/lead.routes.ts +246 -0
  803. package/src/modules/logs/log.model.ts +27 -0
  804. package/src/modules/logs/log.routes.ts +102 -0
  805. package/src/modules/plans/plan.model.ts +45 -0
  806. package/src/modules/surveys/survey.model.ts +70 -0
  807. package/src/modules/surveys/survey.routes.ts +187 -0
  808. package/src/modules/tenants/tenant.model.ts +181 -0
  809. package/src/modules/tenants/tenant.routes.ts +78 -0
  810. package/src/modules/users/user.routes.ts +126 -0
  811. package/src/modules/webhooks/elevenlabs-tool.routes.ts +94 -0
  812. package/src/modules/webhooks/elevenlabs-tool.service.ts +491 -0
  813. package/src/modules/webhooks/elevenlabs.routes.ts +34 -0
  814. package/src/modules/webhooks/elevenlabs.service.ts +565 -0
  815. package/src/modules/webhooks/unipile.routes.ts +917 -0
  816. package/src/modules/whatsapp/appointment.model.ts +47 -0
  817. package/src/modules/whatsapp/operator-request.model.ts +58 -0
  818. package/src/modules/whatsapp/stt-usage.model.ts +30 -0
  819. package/src/modules/whatsapp/whatsapp-chat.model.ts +39 -0
  820. package/src/modules/whatsapp/whatsapp-contact-profile.model.ts +41 -0
  821. package/src/modules/whatsapp/whatsapp-message.model.ts +41 -0
  822. package/src/modules/whatsapp/whatsapp-session.model.ts +60 -0
  823. package/src/modules/whatsapp/whatsapp.routes.ts +1435 -0
  824. package/src/plugins/cors.ts +7 -0
  825. package/src/plugins/jwt.ts +18 -0
  826. package/src/plugins/rawBody.ts +12 -0
  827. package/src/plugins/rbac.ts +24 -0
  828. package/src/routes/admin.routes.ts +208 -0
  829. package/src/routes/health.routes.ts +12 -0
  830. package/src/routes/webhook.routes.ts +12 -0
  831. package/src/services/ai/base.ai.ts +132 -0
  832. package/src/services/ai/gemini.service.ts +41 -0
  833. package/src/services/ai/index.ts +24 -0
  834. package/src/services/ai/openai.service.ts +48 -0
  835. package/src/services/elevenlabs.service.ts +532 -0
  836. package/src/services/google-calendar.service.ts +656 -0
  837. package/src/services/inbound-call-schedule.service.ts +174 -0
  838. package/src/services/netgsm.service.ts +128 -0
  839. package/src/services/unipile.service.ts +200 -0
  840. package/src/services/whatsapp-agent.service.ts +2479 -0
  841. package/src/services/whatsapp.service.ts +245 -0
  842. package/src/templates/index.ts +71 -0
  843. package/src/templates/receptionist.ts +44 -0
  844. package/src/templates/survey.ts +44 -0
  845. package/src/types/index.ts +218 -0
  846. package/src/utils/logger.ts +83 -0
  847. package/tsconfig.json +19 -0
  848. package/web/.dockerignore +4 -0
  849. package/web/Dockerfile +18 -0
  850. package/web/README.md +73 -0
  851. package/web/components.json +23 -0
  852. package/web/eslint.config.js +23 -0
  853. package/web/index.html +14 -0
  854. package/web/nginx.conf +18 -0
  855. package/web/package-lock.json +10292 -0
  856. package/web/package.json +48 -0
  857. package/web/public/favicon.ico +0 -0
  858. package/web/public/vite.svg +1 -0
  859. package/web/src/App.tsx +191 -0
  860. package/web/src/assets/react.svg +1 -0
  861. package/web/src/components/Layout.tsx +261 -0
  862. package/web/src/components/LeadConversation.tsx +251 -0
  863. package/web/src/components/ProtectedRoute.tsx +20 -0
  864. package/web/src/components/conversation-review/CardAudioPlayer.tsx +200 -0
  865. package/web/src/components/conversation-review/CardEvaluation.tsx +351 -0
  866. package/web/src/components/conversation-review/CardMetadata.tsx +44 -0
  867. package/web/src/components/conversation-review/ConversationCard.tsx +95 -0
  868. package/web/src/components/conversation-review/ReviewContainer.tsx +120 -0
  869. package/web/src/components/conversation-review/ReviewFilters.tsx +88 -0
  870. package/web/src/components/empty-state.tsx +15 -0
  871. package/web/src/components/page-header.tsx +19 -0
  872. package/web/src/components/page-loader.tsx +32 -0
  873. package/web/src/components/pagination.tsx +38 -0
  874. package/web/src/components/stat-card.tsx +27 -0
  875. package/web/src/components/status-badge.tsx +125 -0
  876. package/web/src/components/ui/alert.tsx +66 -0
  877. package/web/src/components/ui/badge.tsx +48 -0
  878. package/web/src/components/ui/button.tsx +64 -0
  879. package/web/src/components/ui/card.tsx +92 -0
  880. package/web/src/components/ui/checkbox.tsx +32 -0
  881. package/web/src/components/ui/dialog.tsx +158 -0
  882. package/web/src/components/ui/dropdown-menu.tsx +255 -0
  883. package/web/src/components/ui/input.tsx +21 -0
  884. package/web/src/components/ui/label.tsx +22 -0
  885. package/web/src/components/ui/progress.tsx +29 -0
  886. package/web/src/components/ui/select.tsx +188 -0
  887. package/web/src/components/ui/separator.tsx +28 -0
  888. package/web/src/components/ui/sheet.tsx +123 -0
  889. package/web/src/components/ui/skeleton.tsx +13 -0
  890. package/web/src/components/ui/sonner.tsx +35 -0
  891. package/web/src/components/ui/table.tsx +116 -0
  892. package/web/src/components/ui/tabs.tsx +89 -0
  893. package/web/src/components/ui/textarea.tsx +18 -0
  894. package/web/src/components/ui/tooltip.tsx +57 -0
  895. package/web/src/components/whatsapp/ChatDetail.tsx +417 -0
  896. package/web/src/components/whatsapp/ChatHeader.tsx +78 -0
  897. package/web/src/components/whatsapp/ChatList.tsx +107 -0
  898. package/web/src/components/whatsapp/ChatListItem.tsx +60 -0
  899. package/web/src/components/whatsapp/MessageBubble.tsx +46 -0
  900. package/web/src/components/whatsapp/MessageInput.tsx +63 -0
  901. package/web/src/components/whatsapp/MessageStream.tsx +135 -0
  902. package/web/src/components/whatsapp/SessionDivider.tsx +65 -0
  903. package/web/src/components/whatsapp/SessionHistory.tsx +119 -0
  904. package/web/src/components/whatsapp/settings/AiSettingsTab.tsx +268 -0
  905. package/web/src/components/whatsapp/settings/CalendarTab.tsx +339 -0
  906. package/web/src/components/whatsapp/settings/ConnectionTab.tsx +236 -0
  907. package/web/src/components/whatsapp/settings/NotificationsTab.tsx +109 -0
  908. package/web/src/components/whatsapp/settings/OperatorTab.tsx +303 -0
  909. package/web/src/contexts/AuthContext.tsx +103 -0
  910. package/web/src/index.css +130 -0
  911. package/web/src/lib/api.ts +92 -0
  912. package/web/src/lib/utils.ts +6 -0
  913. package/web/src/main.tsx +10 -0
  914. package/web/src/pages/AppointmentDetail.tsx +206 -0
  915. package/web/src/pages/AppointmentValidation.tsx +157 -0
  916. package/web/src/pages/AppointmentValidationDetail.tsx +617 -0
  917. package/web/src/pages/AppointmentValidationNew.tsx +1005 -0
  918. package/web/src/pages/Appointments.tsx +283 -0
  919. package/web/src/pages/Billing.tsx +126 -0
  920. package/web/src/pages/CallDetail.tsx +293 -0
  921. package/web/src/pages/CallSettings.tsx +313 -0
  922. package/web/src/pages/Calls.tsx +188 -0
  923. package/web/src/pages/CampaignDetail.tsx +216 -0
  924. package/web/src/pages/CampaignNew.tsx +277 -0
  925. package/web/src/pages/CampaignResults.tsx +185 -0
  926. package/web/src/pages/Campaigns.tsx +171 -0
  927. package/web/src/pages/Dashboard.tsx +336 -0
  928. package/web/src/pages/InboundSchedule.tsx +246 -0
  929. package/web/src/pages/LeadDetail.tsx +183 -0
  930. package/web/src/pages/Leads.tsx +258 -0
  931. package/web/src/pages/Login.tsx +99 -0
  932. package/web/src/pages/Register.tsx +129 -0
  933. package/web/src/pages/Settings.tsx +133 -0
  934. package/web/src/pages/SurveyDetail.tsx +232 -0
  935. package/web/src/pages/SurveyPreview.tsx +179 -0
  936. package/web/src/pages/Surveys.tsx +207 -0
  937. package/web/src/pages/Users.tsx +199 -0
  938. package/web/src/pages/WhatsApp.tsx +147 -0
  939. package/web/src/pages/WhatsAppSettings.tsx +215 -0
  940. package/web/src/pages/admin/AdminBaileysMessageLog.tsx +331 -0
  941. package/web/src/pages/admin/AdminBaileysRawMessages.tsx +318 -0
  942. package/web/src/pages/admin/AdminConversationReview.tsx +116 -0
  943. package/web/src/pages/admin/AdminCredits.tsx +467 -0
  944. package/web/src/pages/admin/AdminElevenLabsAgentDetail.tsx +332 -0
  945. package/web/src/pages/admin/AdminElevenLabsAgents.tsx +164 -0
  946. package/web/src/pages/admin/AdminElevenLabsBatchCallDetail.tsx +214 -0
  947. package/web/src/pages/admin/AdminElevenLabsBatchCalls.tsx +293 -0
  948. package/web/src/pages/admin/AdminElevenLabsConversationDetail.tsx +230 -0
  949. package/web/src/pages/admin/AdminElevenLabsConversations.tsx +228 -0
  950. package/web/src/pages/admin/AdminElevenLabsInboundCalls.tsx +293 -0
  951. package/web/src/pages/admin/AdminElevenLabsPhoneNumbers.tsx +506 -0
  952. package/web/src/pages/admin/AdminElevenLabsTestChat.tsx +258 -0
  953. package/web/src/pages/admin/AdminElevenLabsWebhooks.tsx +289 -0
  954. package/web/src/pages/admin/AdminEvaluationDashboard.tsx +520 -0
  955. package/web/src/pages/admin/AdminLeads.tsx +339 -0
  956. package/web/src/pages/admin/AdminLogs.tsx +283 -0
  957. package/web/src/pages/admin/AdminMigrations.tsx +247 -0
  958. package/web/src/pages/admin/AdminPlans.tsx +313 -0
  959. package/web/src/pages/admin/AdminSystem.tsx +391 -0
  960. package/web/src/pages/admin/AdminTenantBillableItems.tsx +464 -0
  961. package/web/src/pages/admin/AdminTenantDetail.tsx +1317 -0
  962. package/web/src/pages/admin/AdminTenants.tsx +274 -0
  963. package/web/src/pages/admin/AdminWhatsApp.tsx +618 -0
  964. package/web/src/pages/admin/AdminWhatsAppTestChat.tsx +328 -0
  965. package/web/src/types/index.ts +242 -0
  966. package/web/tsconfig.app.json +32 -0
  967. package/web/tsconfig.json +13 -0
  968. package/web/tsconfig.node.json +26 -0
  969. 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).