feat: team module, multi-stage Dockerfile, doctor utils, AI config overhaul

- Team module: POST /api/team/members (in-place employee creation with
  temp password + Redis cache), PUT /api/team/members/:id, GET temp
  password endpoint. Uses signUpInWorkspace — no email invites.
- Dockerfile: rewritten as multi-stage build (builder + runtime) so
  native modules compile for target arch. Fixes darwin→linux crash.
- .dockerignore: exclude dist, node_modules, .env, .git, data/
- package-lock.json: regenerated against public npmjs.org (was
  pointing at localhost:4873 Verdaccio — broke docker builds)
- Doctor utils: shared DOCTOR_VISIT_SLOTS_FRAGMENT + normalizeDoctors
  helper for visit-slot-aware queries across 6 consumers
- AI config: full admin CRUD (GET/PUT/POST reset), workspace-scoped
  setup-state with workspace ID isolation, AI prompt defaults overhaul
- Agent config: camelCase field fix for SDK-synced workspaces
- Session service: workspace-scoped Redis key prefixing for setup state
- Recordings/supervisor/widget services: updated to use doctor-utils
  shared fragments instead of inline visitingHours queries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 08:37:58 +05:30
parent eacfce6970
commit 695f119c2b
25 changed files with 2756 additions and 1936 deletions

View File

@@ -3,6 +3,7 @@ import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { ConfigService } from '@nestjs/config';
import type { WidgetInitResponse, WidgetBookRequest, WidgetLeadRequest } from './widget.types';
import { ThemeService } from '../config/theme.service';
import { DOCTOR_VISIT_SLOTS_FRAGMENT, normalizeDoctors, type NormalizedDoctor } from '../shared/doctor-utils';
// Dedup window: any lead created for this phone within the last 24h is
// considered the same visitor's lead — chat + book + contact by the same
@@ -131,16 +132,22 @@ export class WidgetService {
};
}
async getDoctors(): Promise<any[]> {
// Returns NormalizedDoctor[] — the raw GraphQL fields plus three
// derived bridge fields (`clinics`, `clinic`, `visitingHours`)
// built from the visit-slots reverse relation. See
// shared/doctor-utils.ts for the rationale and the format of the
// visiting-hours summary string.
async getDoctors(): Promise<NormalizedDoctor[]> {
const data = await this.platform.queryWithAuth<any>(
`{ doctors(first: 50) { edges { node {
id name fullName { firstName lastName } department specialty visitingHours
id name fullName { firstName lastName } department specialty
consultationFeeNew { amountMicros currencyCode }
clinic { clinicName }
${DOCTOR_VISIT_SLOTS_FRAGMENT}
} } } }`,
undefined, this.auth,
);
return data.doctors.edges.map((e: any) => e.node);
const raws = data.doctors.edges.map((e: any) => e.node);
return normalizeDoctors(raws);
}
async getSlots(doctorId: string, date: string): Promise<any> {