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

@@ -17,8 +17,11 @@ export class SetupStateController {
constructor(private readonly setupState: SetupStateService) {}
@Get('setup-state')
getState() {
const state = this.setupState.getState();
async getState() {
// Use the checked variant so the platform workspace probe runs
// before we serialize. Catches workspace changes (DB resets,
// re-onboards) on the very first frontend GET.
const state = await this.setupState.getStateChecked();
return {
...state,
wizardRequired: this.setupState.isWizardRequired(),
@@ -30,19 +33,24 @@ export class SetupStateController {
@Param('step') step: SetupStepName,
@Body() body: { completed: boolean; completedBy?: string },
) {
if (body.completed) {
return this.setupState.markStepCompleted(step, body.completedBy ?? null);
}
return this.setupState.markStepIncomplete(step);
const updated = body.completed
? this.setupState.markStepCompleted(step, body.completedBy ?? null)
: this.setupState.markStepIncomplete(step);
// Mirror GET shape — include `wizardRequired` so the frontend
// doesn't see a state object missing the field and re-render
// into an inconsistent shape.
return { ...updated, wizardRequired: this.setupState.isWizardRequired() };
}
@Post('setup-state/dismiss')
dismiss() {
return this.setupState.dismissWizard();
const updated = this.setupState.dismissWizard();
return { ...updated, wizardRequired: this.setupState.isWizardRequired() };
}
@Post('setup-state/reset')
reset() {
return this.setupState.resetState();
const updated = this.setupState.resetState();
return { ...updated, wizardRequired: this.setupState.isWizardRequired() };
}
}