|

Medspa architecture and workflows

Keystone schema by domain, the booking-to-checkout lifecycle, clinical gates, named GraphQL operations, and role capabilities.

Architecture

LayerLocationRole
Keystone schemafeatures/keystone/models/**98 lists, all scoped to a Practice
Workflow mutations and projectionsfeatures/keystone/mutations/**, features/keystone/extendGraphqlSchema/**Named GraphQL operations registered in features/keystone/mutations/index.ts
Accessfeatures/keystone/access.ts, features/keystone/models/tenantAccess.tsCapability checks and practice tenancy
Operator dashboardfeatures/dashboard (shell), features/platform (Medspa workspaces), app/dashboard/** (routes)Staff UI
Storefrontfeatures/storefront, app/(storefront)/(main)/**Client UI
Paymentsfeatures/integrations/paymentManual and Stripe adapters

Almost every list is write-locked. The lists with direct GraphQL CRUD are Service, ServiceCategory, ServiceVariant, MedSpaImage, WidgetConfiguration (canManagePractice), Vendor (canManageInventory), MessageTemplate (canManageCommunications), and PaymentProvider (canManageIntegrations). Practice and User allow updates only. Everything else is written by a named mutation or as a side effect of one, and a dashboard page for those lists must call the mutation rather than create<List>.

Data model

  • Practice and access: Practice (tenant), Location, StorefrontSetting, WidgetConfiguration, User, Role, StaffInvitation, PracticeMembership, Provider, AuditEvent.
  • Catalog: ServiceCategory, Service (duration, price, deposit, cleanup buffer, and flags such as requiresConsultation, requiresIntakeReview, staffAuthorizationRequired, requiresLotTraceability), ServiceVariant, MedSpaImage, Resource (rooms and devices with a readiness status), ResourceScheduleBlock, DeviceReadinessEvent.
  • Availability and booking: ProviderSchedule (recurring, one-time, time off), Client, Appointment, AppointmentLineItem (service snapshot), AppointmentResourceReservation, AppointmentSupervisorReservation, WaitlistEntry.
  • Clinical record: IntakeForm, FormTemplate, FormSubmission, IntakeReviewAction, Consultation, ContraindicationFinding, ContraindicationDecision, TreatmentPlan, TreatmentPlanItem, ClinicalOrder, ClinicalOrderAction, ConsentTemplateVersion, ConsentRecord, ConsentAction, TreatmentSession, TreatmentAdministration, AdministrationLot, ClientNote, ClientAsset, AssetAccessEvent, PhotoConsent, PhotoConsentAction, ProviderCredential, ProviderAuthorization, ClinicalAuthorityEvent, FollowUpTask, FollowUpAction.
  • Money and benefits: MedSpaTransaction, MedSpaTransactionLineItem, Payment, PaymentSession, Refund, PaymentProvider, PaymentWebhookEvent, WebhookInbox, Commission, GiftCard, GiftCardTransaction, Membership, ClientMembership, MembershipAgreement, MembershipLedgerEntry, ServicePackage, ClientPackage, ClientPackageUsage, ClientPackageAdjustment, and the schema-only LoyaltyProgram, ClientPoints, Referral.
  • Inventory: Product, Vendor, PurchaseOrder, PurchaseOrderLine, GoodsReceipt, GoodsReceiptLine, InventoryLot, StockMovement (lot level), InventoryMovement (product level), and the legacy ProductSale.
  • Safety and governance: Incident, IncidentAction, RecallCase, RecallExposure, RecallAction, PatientRightsRequest, PatientRightsAction, LegalHold, RetentionPolicy, BreakGlassEvent.
  • Communications and system: MessageTemplate, MessageLog, Review, DomainEvent, OutboxEvent.

The repository's docs/schema-map.md lists each model's key fields, who creates it, and its states.

Booking to checkout workflow

  1. Book. A client books on the storefront, or staff book from /dashboard/platform/appointments/new. bookAppointment checks availability, creates the Client and User if needed, the Appointment in pending, AppointmentLineItem snapshots, and room, device, and supervisor reservations. A guest booking returns a link token that lets the guest view and cancel that request (storefrontAppointment, cancelGuestAppointment). No payment is taken at booking.
  2. Waitlist. Demand without a slot goes to WaitlistEntry through joinWaitlist (staff) or requestWaitlistSpot (storefront). Entries move active to notified to converted or removed through transitionWaitlistEntry.
  3. Confirm and arrive. transitionAppointment allows pending to confirmed, cancelled, or no_show; confirmed to in_progress, cancelled, or no_show; and in_progress to cancelled. Changes are revision-checked.
  4. Intake. The client submits intake from /account/intake/[appointmentId] or staff record it (submitIntake). Clinical staff review it with reviewIntake (submitted, in_review, reviewed, superseded).
  5. Check out. completeAppointmentCheckout accepts a confirmed or in_progress appointment, applies membership, package, and manual discounts, retail lines, tip, and tax, records the MedSpaTransaction, Payment, and PaymentSession, writes retail InventoryMovement and Commission records, and sets the appointment to completed. Current checkout supports operator-recorded cash only.
  6. Aftercare. scheduleFollowUp creates a FollowUpTask; staff append actions and clients reply with submitFollowUpResponse. Clients can review a completed visit with submitAppointmentReview, and staff approve or hide reviews with moderateReview.

Clinical treatment workflow

Treatments flagged as clinical pass through recorded gates before execution. The treatment workspace at /dashboard/platform/appointments/[id]/treatment drives these mutations:

  1. recordConsultation records the assessment and candidacy (candidate, deferred, declined, referred).
  2. recordContraindicationFinding and decideContraindication record rule hits and the clinician's disposition.
  3. approveTreatmentPlan creates the TreatmentPlan and items (draft, active, paused, completed, cancelled).
  4. issueClinicalOrder authorizes a performer; transitionClinicalOrder moves it through active, partially_fulfilled, fulfilled, held, revoked, expired, or entered_in_error.
  5. signTreatmentConsent records consent against a published ConsentTemplateVersion (publishConsentTemplate); withdrawTreatmentConsent withdraws it.
  6. prepareTreatmentSession and startTreatmentSession move the TreatmentSession from planned to ready to in_progress.
  7. recordTreatmentAdministration records what was administered and, for lot-traced products, the AdministrationLot and StockMovement.
  8. appendSignedClinicalNote adds an append-only, supersedable ClientNote.

Before a session can run, features/keystone/mutations/clinicalExecutionReadiness.ts checks that the exact approved plan, item, and consultation still authorize the booked encounter; that a current clinical order and matching consent exist; that current reviewed intake covers every required template version; that no contraindication review blocks treatment; that the session falls inside the appointment window; and that no required resource has an overlapping maintenance or unavailable block. Provider credentials (registerProviderCredential) and per-treatment, per-location authority (configureProviderAuthorization, transitionProviderAuthority) are checked separately.

These checks confirm that records exist and match. They do not verify a license with an issuing board, judge clinical appropriateness, or replace the supervising clinician.

Other operator workflows

  • Staff: upsertAccessRole, inviteStaff, acceptStaffInvitation, createProviderProfile, updateProviderProfile.
  • Practice setup: upsertLocation, upsertResource, setResourceAvailability, transitionDeviceReadiness, upsertProviderSchedule, setProviderScheduleAvailability, scheduleAppointmentResources, publishStorefrontSettings.
  • Forms: upsertIntakeForm, upsertFormTemplate, publishConsentTemplate.
  • Benefits: upsertMembershipPlan, enrollClientMembership, acceptMembershipAgreement, transitionMembership, upsertServicePackage, sellClientPackage, redeemClientPackage, reverseClientPackageUsage, issueGiftCard, applyGiftCardBalance.
  • Inventory: upsertProduct, draftPurchaseOrder, transitionPurchaseOrder (draft, submitted, partially_received, received, cancelled), receivePurchaseOrder (creates GoodsReceipt and InventoryLot records), dispositionInventoryLot, returnRetailLot, adjustMedSpaInventory.
  • Money: refundPayment, distributeTip, payoutCommissions.
  • Clients: updateClientProfile.
  • Safety: reportIncident, appendIncidentAction, openRecallCase, appendRecallAction.
  • Privacy: requestPatientRights, appendPatientRightsAction, placeLegalHold, releaseLegalHold, publishRetentionPolicy, openBreakGlass.
  • Clinical assets: grantPhotoConsent, withdrawPhotoConsent, createClinicalAssetIntent, markClinicalAssetReady, authorizeClinicalAssetAccess, releaseClinicalAsset, tombstoneClinicalAsset.

Custom GraphQL queries

  • Storefront: publicStorefrontSettings, storefrontServices, storefrontProviders, storefrontProvider, checkAvailability, storefrontReviews, storefrontAppointment, clientPortal, clientIntakeForms.
  • Operator projections: medspaOperationsWorkspace, medspaAppointmentSchedule, medspaAppointmentClinicalHeader, medspaPlanJourney, medspaConsultationQueue, medspaClientDirectory, medspaClientRecord, medspaWaitlistBoard, medspaMessageLog, medspaReviewBoard, calculateCommission.
  • Auth: registerCustomer plus Keystone's password authentication and reset mutations.

Mutations that change money or clinical state take an idempotency key and, where relevant, a record revision, and many run in serializable transactions (serializableWorkflow.ts).

Roles and access control

Each Role carries these capabilities: canManagePeople, canManageRoles, canAccessDashboard, canManageOnboarding, canManagePractice, canSchedule, canReadClients, canManageClinical, canManageSensitiveRecords, canManageInventory, canManageFinance, canManageCommunications, canViewReports, and canManageIntegrations.

Access checks re-read the user and role on each request and deny access when the user is disabled, has moved practice, or the role no longer grants the flag carried in the session. Clinical and governance lists (ClientNote, consent, incidents, patient rights, legal holds) require canManageClinical or canManageSensitiveRecords rather than general dashboard access.

On this page