Compare commits

..
12 Commits
Author SHA1 Message Date
Summit Dental CareandClaude Sonnet 5 34e9c83a63 feat: add insurance provider dropdown to eligibility page, save to patient record after eligibility check
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:49:54 -04:00
Summit Dental CareandClaude Sonnet 5 a3d2960c98 feat: rename DDMA to ClaimMD across selenium service, remove MH/CMSP eligibility feature
Rename ddma_browser_manager/helpers/worker files to claimmd_* naming in
SeleniumService. Remove the MassHealth (MH) and CMSP eligibility-check
feature end to end: buttons and dead placeholder buttons on the insurance
eligibility page, their handlers and auto-trigger wiring, the chatbot's
MassHealth-only manual paste flow, backend routes/queue job types/processors,
and the corresponding Python Selenium worker endpoints. Multi-provider
eligibility checking (DDMA, Delta, Tufts SCO, United SCO, CCA, BCBS MA) and
the Twilio SMS eligibility flow are untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:26:17 -04:00
GiteadandClaude Sonnet 4.6 00fd213291 feat: add Dental Office Name to Lab RX form, pre-filled from office contact settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 21:21:59 -04:00
GiteadandClaude Sonnet 4.6 191ed129d8 feat: replace DOB with Age in Lab RX form, auto-calculated from patient DOB
Age is pre-filled when opening the RX popup by calculating from patient.dateOfBirth.
The field is editable in case of override. Both preview card and print output
now show Age instead of DOB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 21:06:44 -04:00
GiteadandClaude Sonnet 4.6 305a07874d fix: add selenium-settings-card.jsx and wire it into settings-page.jsx
selenium-settings-card.jsx didn't exist and settings-page.jsx was missing
the import and case "selenium" handler, so the Selenium settings section
was invisible on other PCs running the .jsx files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:13:49 -04:00
GiteadandClaude Sonnet 4.6 6e779fc630 fix: update lab-management-tab.jsx and add lab-rx-modal.jsx
JSX files were missing the Lab RX feature — lab-management-tab.jsx still had
the old lab order table UI, and lab-rx-modal.jsx didn't exist. The app on
other PCs runs .jsx files, so git pull wasn't showing the new Lab RX feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:12:10 -04:00
GiteadandClaude Sonnet 4.6 ce2e03e505 feat: pre-fill doctor name with default NPI provider in Lab RX popup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 23:55:23 -04:00
GiteadandClaude Sonnet 4.6 75ecd7cc20 feat: add Selenium Settings page to configure paymentGroupId per office
Adds a new Settings → Advanced → Selenium Settings page where the office
name (paymentGroupId) used in United/DentalHub portal dropdowns can be
configured without touching code. Previously hardcoded as "Summit Dental
Care"; now injected from DB into the three United workers at job dispatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 23:53:43 -04:00
GiteadandClaude Sonnet 4.6 abb2a0ac96 feat: add Lab RX feature with template management and auto-seed defaults
- New lab_rx_template table with sortOrder and Prisma migrations
- Backend CRUD + reorder routes at /api/lab-rx/templates
- Auto-seeds 3 Highland Dental Studio defaults on first use per user
- LabRxModal: template picker, inline rename, up/down reorder, full
  template form with sections (Template Name / Lab Info / Case Details)
- RX popup: Tooth Number, Shade, Due Date, Doctor (from NPI providers),
  Instructions pre-filled with "Please make / Thank you!", print output
- lab-rx-templates.ts config file for future git-managed additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 23:20:44 -04:00
GiteadandClaude Sonnet 4.6 ac4d8c4c74 feat: add lab management layout, barcode generation, and dental education page
- Redesign lab management tab with patient search table and action buttons
- Add react-barcode integration: generates CODE128 barcode from patient name with print and download support
- Add Dental Education page and sidebar entry between Cloud Storage and AI Input Agent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:03:58 -04:00
GiteadandClaude Sonnet 4.6 998ef0ac95 feat: add POE and period exam aliases → D0120 in CDT lookup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:58:56 -04:00
GiteadandClaude Sonnet 4.6 15a1091f35 fix: stop premature chatbot claim queue advance + skip preview popup mid-batch
handleClaimSubmit advanced the chatbot batch claim queue right after creating
the DB claim record, before the Selenium job even ran. This caused only the
last patient in a multi-patient claim batch to get its PDF saved to
Documents — earlier patients' job tracking got overwritten by the next
patient's submission before their PDF download could complete.

Also make advanceChatbotClaimQueue() report whether it navigated to another
queued patient, and skip the auto PDF preview popup in that case so it only
opens for the last/standalone claim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 23:44:21 -04:00
522 changed files with 12322 additions and 5281 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"is_task_list_visible": true, "is_task_list_visible": true,
"active_task": "backend#dev" "active_task": "frontend#dev"
} }
+4
View File
@@ -44,6 +44,8 @@ const ALIAS_MAP: Record<string, string> = {
// Exams // Exams
"perio exam": "periodic oral evaluation", "perio exam": "periodic oral evaluation",
"periodic exam": "periodic oral evaluation", "periodic exam": "periodic oral evaluation",
"period exam": "periodic oral evaluation",
"poe": "periodic oral evaluation",
"recall exam": "periodic oral evaluation", "recall exam": "periodic oral evaluation",
"comp exam": "comprehensive oral evaluation", "comp exam": "comprehensive oral evaluation",
"comprehensive exam": "comprehensive oral evaluation", "comprehensive exam": "comprehensive oral evaluation",
@@ -150,6 +152,8 @@ const ALIAS_MAP: Record<string, string> = {
// or short abbreviations that need precise mapping (checked before keyword search) // or short abbreviations that need precise mapping (checked before keyword search)
const DIRECT_CODE_MAP: Record<string, string> = { const DIRECT_CODE_MAP: Record<string, string> = {
// Exams // Exams
"poe": "D0120",
"period exam": "D0120",
"limited exam": "D0140", "limited exam": "D0140",
"emergency exam": "D0140", "emergency exam": "D0140",
// X-rays // X-rays
-33
View File
@@ -7,7 +7,6 @@
*/ */
import { InProcessQueue } from "./inProcessQueue"; import { InProcessQueue } from "./inProcessQueue";
import { io } from "../socket"; import { io } from "../socket";
import { runEligibilityProcessor } from "./processors/eligibilityProcessor";
import { runClaimStatusProcessor } from "./processors/claimStatusProcessor"; import { runClaimStatusProcessor } from "./processors/claimStatusProcessor";
import { runClaimSubmitProcessor } from "./processors/claimSubmitProcessor"; import { runClaimSubmitProcessor } from "./processors/claimSubmitProcessor";
import { runOcrProcessor } from "./processors/ocrProcessor"; import { runOcrProcessor } from "./processors/ocrProcessor";
@@ -23,8 +22,6 @@ import { runUnitedDHClaimProcessor } from "./processors/unitedDHClaimProcessor";
import { runUnitedDHPreAuthProcessor } from "./processors/unitedDHPreAuthProcessor"; import { runUnitedDHPreAuthProcessor } from "./processors/unitedDHPreAuthProcessor";
import { runTuftsSCOPreAuthProcessor } from "./processors/tuftsSCOPreAuthProcessor"; import { runTuftsSCOPreAuthProcessor } from "./processors/tuftsSCOPreAuthProcessor";
import { runTuftsSCOClaimProcessor } from "./processors/tuftsSCOClaimProcessor"; import { runTuftsSCOClaimProcessor } from "./processors/tuftsSCOClaimProcessor";
import { runEligibilityHistoryProcessor } from "./processors/eligibilityHistoryProcessor";
import { runCmspEligibilityHistoryRemainingProcessor } from "./processors/cmspEligibilityHistoryRemainingProcessor";
import { runBcbsMaEligibilityProcessor } from "./processors/bcbsMaEligibilityProcessor"; import { runBcbsMaEligibilityProcessor } from "./processors/bcbsMaEligibilityProcessor";
import type { SeleniumJobData, OcrJobData } from "./queues"; import type { SeleniumJobData, OcrJobData } from "./queues";
@@ -59,16 +56,6 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
message: "Selenium browser starting…", message: "Selenium browser starting…",
}); });
if (jobType === "eligibility-check") {
return runEligibilityProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "claim-status-check") { if (jobType === "claim-status-check") {
return runClaimStatusProcessor({ return runClaimStatusProcessor({
enrichedPayload: data.enrichedPayload, enrichedPayload: data.enrichedPayload,
@@ -231,26 +218,6 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
job.id job.id
); );
} }
if (jobType === "cmsp-eligibility-history-remaining-check") {
return runCmspEligibilityHistoryRemainingProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "mh-eligibility-history-check") {
return runEligibilityHistoryProcessor({
enrichedPayload: data.enrichedPayload,
userId: data.userId,
insuranceId: data.insuranceId!,
formFirstName: data.formFirstName,
formLastName: data.formLastName,
formDob: data.formDob,
});
}
if (jobType === "bcbs-ma-eligibility-check") { if (jobType === "bcbs-ma-eligibility-check") {
return runBcbsMaEligibilityProcessor( return runBcbsMaEligibilityProcessor(
{ {
@@ -1,169 +0,0 @@
/**
* Processor for "cmsp-eligibility-history-remaining-check" jobs.
* Saves eligibility PDF, service history PDF, and accumulator PDF
* to the patient's ELIGIBILITY_STATUS document group.
*/
import fs from "fs/promises";
import path from "path";
import { storage } from "../../storage";
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
import {
callPythonSync,
splitName,
createOrUpdatePatientByInsuranceId,
} from "./_shared";
export interface CmspEligibilityHistoryRemainingProcessorInput {
enrichedPayload: any;
userId: number;
insuranceId: string;
formFirstName?: string;
formLastName?: string;
formDob?: string;
}
export interface CmspEligibilityHistoryRemainingProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFilename?: string | null;
memberDetailsPdfFileId?: number | null;
memberDetailsPdfFilename?: string | null;
historyPdfFileId?: number | null;
historyPdfFilename?: string | null;
accumulatorPdfFileId?: number | null;
accumulatorPdfFilename?: string | null;
}
export async function runCmspEligibilityHistoryRemainingProcessor(
input: CmspEligibilityHistoryRemainingProcessorInput
): Promise<CmspEligibilityHistoryRemainingProcessorResult> {
const { enrichedPayload, userId, insuranceId, formFirstName, formLastName, formDob } = input;
const seleniumResult = await callPythonSync("/cmsp-eligibility-history-remaining-check", {
data: enrichedPayload,
});
const outputResult: CmspEligibilityHistoryRemainingProcessorResult = {};
// Extract patient name
const seleniumFirst = seleniumResult.firstName?.trim() || null;
const seleniumLast = seleniumResult.lastName?.trim() || null;
const seleniumName = seleniumResult.name?.trim() || null;
const seleniumInsurance = seleniumResult.insurance?.trim() || null;
const extracted: { firstName?: string | null; lastName?: string | null } = {};
if (seleniumFirst || seleniumLast) {
extracted.firstName = seleniumFirst;
extracted.lastName = seleniumLast;
} else if (seleniumName) {
const parts = splitName(seleniumName);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
if (!extracted.firstName && !extracted.lastName && seleniumResult?.pdf_path?.endsWith(".pdf")) {
try {
const pdfBuffer = await fs.readFile(seleniumResult.pdf_path);
const extraction = await forwardToPatientDataExtractorService({
buffer: pdfBuffer,
originalname: path.basename(seleniumResult.pdf_path),
mimetype: "application/pdf",
} as any);
if (extraction.name) {
const parts = splitName(extraction.name);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
} catch (e) {
console.error("[cmspProcessor] PDF name extraction failed:", e);
}
}
const preferFirst = extracted.firstName || formFirstName || null;
const preferLast = extracted.lastName || formLastName || null;
let patient;
try {
patient = await createOrUpdatePatientByInsuranceId({
insuranceId,
firstName: preferFirst,
lastName: preferLast,
dob: formDob,
userId,
});
} catch (e: any) {
throw new Error(`Failed to create/update patient: ${e.message}`);
}
if (patient && patient.id !== undefined) {
let newStatus = "UNKNOWN";
if (seleniumResult.eligibility === "Y") newStatus = "ACTIVE";
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
const updates: any = { status: newStatus };
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
await storage.updatePatient(patient.id, updates);
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// Helper: save a single PDF to the patient's ELIGIBILITY_STATUS group
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
try {
const buffer = await fs.readFile(pdfPath);
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
if (created && typeof created === "object" && "id" in created) {
return Number(created.id);
}
return null;
} catch (e: any) {
console.error("[cmspProcessor] saveToGroup failed:", e.message);
return null;
}
};
// Save eligibility PDF
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
outputResult.pdfFileId = await saveToGroup(seleniumResult.pdf_path);
outputResult.pdfFilename = path.basename(seleniumResult.pdf_path);
outputResult.pdfUploadStatus = outputResult.pdfFileId ? "Eligibility PDF saved" : "Eligibility PDF upload failed";
} else {
outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium";
}
// Save member details PDF
if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) {
outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path);
outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path);
}
// Save history PDF
if (seleniumResult.history_pdf_path?.endsWith(".pdf")) {
outputResult.historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path);
outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path);
}
// Save accumulator PDF
if (seleniumResult.accumulator_pdf_path?.endsWith(".pdf")) {
outputResult.accumulatorPdfFileId = await saveToGroup(seleniumResult.accumulator_pdf_path);
outputResult.accumulatorPdfFilename = path.basename(seleniumResult.accumulator_pdf_path);
}
} else {
outputResult.patientUpdateStatus = "Patient not found or missing ID; no update performed";
}
// Cleanup temp files
try {
if (seleniumResult.pdf_path) await emptyFolderContainingFile(seleniumResult.pdf_path);
} catch (e) {
console.error("[cmspProcessor] cleanup failed:", e);
}
return outputResult;
}
@@ -1,167 +0,0 @@
/**
* Processor for "mh-eligibility-history-check" jobs.
* Runs MH eligibility + service history selenium flow and saves both PDFs
* to the patient's ELIGIBILITY_STATUS document group.
*/
import fs from "fs/promises";
import path from "path";
import { storage } from "../../storage";
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
import {
callPythonSync,
splitName,
createOrUpdatePatientByInsuranceId,
} from "./_shared";
export interface EligibilityHistoryProcessorInput {
enrichedPayload: any;
userId: number;
insuranceId: string;
formFirstName?: string;
formLastName?: string;
formDob?: string;
}
export interface EligibilityHistoryProcessorResult {
patientUpdateStatus?: string;
pdfUploadStatus?: string;
pdfFileId?: number | null;
pdfFilename?: string | null;
memberDetailsPdfFileId?: number | null;
memberDetailsPdfFilename?: string | null;
historyPdfFileId?: number | null;
historyPdfFilename?: string | null;
}
export async function runEligibilityHistoryProcessor(
input: EligibilityHistoryProcessorInput
): Promise<EligibilityHistoryProcessorResult> {
const { enrichedPayload, userId, insuranceId, formFirstName, formLastName, formDob } = input;
const seleniumResult = await callPythonSync("/mh-eligibility-history-check", {
data: enrichedPayload,
});
const outputResult: EligibilityHistoryProcessorResult = {};
// Extract patient name
const seleniumFirst = seleniumResult.firstName?.trim() || null;
const seleniumLast = seleniumResult.lastName?.trim() || null;
const seleniumName = seleniumResult.name?.trim() || null;
const seleniumInsurance = seleniumResult.insurance?.trim() || null;
const extracted: { firstName?: string | null; lastName?: string | null } = {};
if (seleniumFirst || seleniumLast) {
extracted.firstName = seleniumFirst;
extracted.lastName = seleniumLast;
} else if (seleniumName) {
const parts = splitName(seleniumName);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
if (!extracted.firstName && !extracted.lastName && seleniumResult?.pdf_path?.endsWith(".pdf")) {
try {
const pdfBuffer = await fs.readFile(seleniumResult.pdf_path);
const extraction = await forwardToPatientDataExtractorService({
buffer: pdfBuffer,
originalname: path.basename(seleniumResult.pdf_path),
mimetype: "application/pdf",
} as any);
if (extraction.name) {
const parts = splitName(extraction.name);
extracted.firstName = parts.firstName || null;
extracted.lastName = parts.lastName || null;
}
} catch (e) {
console.error("[eligibilityHistoryProcessor] PDF name extraction failed:", e);
}
}
const preferFirst = extracted.firstName || formFirstName || null;
const preferLast = extracted.lastName || formLastName || null;
let patient;
try {
patient = await createOrUpdatePatientByInsuranceId({
insuranceId,
firstName: preferFirst,
lastName: preferLast,
dob: formDob,
userId,
});
} catch (e: any) {
throw new Error(`Failed to create/update patient: ${e.message}`);
}
if (patient && patient.id !== undefined) {
let newStatus = "UNKNOWN";
if (seleniumResult.eligibility === "Y") newStatus = "ACTIVE";
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
const updates: any = { status: newStatus };
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
await storage.updatePatient(patient.id, updates);
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
// Helper: save a PDF buffer to the patient's ELIGIBILITY_STATUS group
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
try {
const buffer = await fs.readFile(pdfPath);
const groupTitleKey = "ELIGIBILITY_STATUS";
const groupTitle = "Eligibility Status";
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
if (!group?.id) throw new Error("PDF group creation failed");
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
if (created && typeof created === "object" && "id" in created) {
return Number(created.id);
}
return null;
} catch (e: any) {
console.error("[eligibilityHistoryProcessor] saveToGroup failed:", e.message);
return null;
}
};
// Save eligibility PDF
let eligibilityPdfFileId: number | null = null;
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
eligibilityPdfFileId = await saveToGroup(seleniumResult.pdf_path);
outputResult.pdfUploadStatus = eligibilityPdfFileId
? "Eligibility PDF saved"
: "Eligibility PDF upload failed";
outputResult.pdfFilename = path.basename(seleniumResult.pdf_path);
} else {
outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium";
}
outputResult.pdfFileId = eligibilityPdfFileId;
// Save member details PDF
if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) {
outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path);
outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path);
}
// Save history PDF
let historyPdfFileId: number | null = null;
if (seleniumResult.history_pdf_path?.endsWith(".pdf")) {
historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path);
outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path);
}
outputResult.historyPdfFileId = historyPdfFileId;
} else {
outputResult.patientUpdateStatus = "Patient not found or missing ID; no update performed";
}
// Cleanup temp files
try {
if (seleniumResult.pdf_path) await emptyFolderContainingFile(seleniumResult.pdf_path);
} catch (e) {
console.error("[eligibilityHistoryProcessor] cleanup failed:", e);
}
return outputResult;
}
-3
View File
@@ -3,7 +3,6 @@ import { redisConnection } from "./connection";
/** Job types dispatched to the selenium Python worker. */ /** Job types dispatched to the selenium Python worker. */
export type SeleniumJobType = export type SeleniumJobType =
| "eligibility-check"
| "claim-status-check" | "claim-status-check"
| "claim-submit" | "claim-submit"
| "claim-pre-auth" | "claim-pre-auth"
@@ -19,8 +18,6 @@ export type SeleniumJobType =
| "uniteddh-preauth-submit" | "uniteddh-preauth-submit"
| "tuftssco-preauth-submit" | "tuftssco-preauth-submit"
| "tuftssco-eligibility-check" | "tuftssco-eligibility-check"
| "mh-eligibility-history-check"
| "cmsp-eligibility-history-remaining-check"
| "bcbs-ma-eligibility-check"; | "bcbs-ma-eligibility-check";
export interface SeleniumJobData { export interface SeleniumJobData {
@@ -2,7 +2,6 @@ import { Worker, Job } from "bullmq";
import { redisConnection } from "../connection"; import { redisConnection } from "../connection";
import { SeleniumJobData } from "../queues"; import { SeleniumJobData } from "../queues";
import { io } from "../../socket"; import { io } from "../../socket";
import { runEligibilityProcessor } from "../processors/eligibilityProcessor";
import { runClaimStatusProcessor } from "../processors/claimStatusProcessor"; import { runClaimStatusProcessor } from "../processors/claimStatusProcessor";
import { runClaimSubmitProcessor } from "../processors/claimSubmitProcessor"; import { runClaimSubmitProcessor } from "../processors/claimSubmitProcessor";
@@ -27,7 +26,7 @@ function emitJobUpdate(
} }
async function processSeleniumJob(job: Job<SeleniumJobData>) { async function processSeleniumJob(job: Job<SeleniumJobData>) {
const { jobType, userId, socketId, enrichedPayload } = job.data; const { jobType, socketId, enrichedPayload } = job.data;
const jobId = job.id ?? job.name; const jobId = job.id ?? job.name;
emitJobUpdate(socketId, jobId, jobType, "active", { emitJobUpdate(socketId, jobId, jobType, "active", {
@@ -37,16 +36,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
try { try {
let result: any; let result: any;
if (jobType === "eligibility-check") { if (jobType === "claim-status-check") {
result = await runEligibilityProcessor({
enrichedPayload,
userId,
insuranceId: job.data.insuranceId!,
formFirstName: job.data.formFirstName,
formLastName: job.data.formLastName,
formDob: job.data.formDob,
});
} else if (jobType === "claim-status-check") {
result = await runClaimStatusProcessor({ result = await runClaimStatusProcessor({
enrichedPayload, enrichedPayload,
insuranceId: job.data.insuranceId!, insuranceId: job.data.insuranceId!,
+4
View File
@@ -43,6 +43,8 @@ import shoppingVendorsRoutes from "./shopping-vendors";
import feeScheduleRoutes from "./feeSchedule"; import feeScheduleRoutes from "./feeSchedule";
import licenseRoutes from "./license"; import licenseRoutes from "./license";
import insuranceStatusBcbsMaRoutes from "./insuranceStatusBcbsMa"; import insuranceStatusBcbsMaRoutes from "./insuranceStatusBcbsMa";
import labRxRoutes from "./lab-rx";
import seleniumSettingsRoutes from "./selenium-settings";
const router = Router(); const router = Router();
@@ -90,5 +92,7 @@ router.use("/commissions", commissionsRoutes);
router.use("/shopping-vendors", shoppingVendorsRoutes); router.use("/shopping-vendors", shoppingVendorsRoutes);
router.use("/fee-schedule", feeScheduleRoutes); router.use("/fee-schedule", feeScheduleRoutes);
router.use("/license", licenseRoutes); router.use("/license", licenseRoutes);
router.use("/lab-rx", labRxRoutes);
router.use("/selenium-settings", seleniumSettingsRoutes);
export default router; export default router;
+1 -155
View File
@@ -15,7 +15,7 @@ import {
} from "../../../../packages/db/types/patient-types"; } from "../../../../packages/db/types/patient-types";
import { formatDobForAgent } from "../utils/dateUtils"; import { formatDobForAgent } from "../utils/dateUtils";
import { seleniumQueue } from "../queue/queues"; import { seleniumQueue } from "../queue/queues";
import { enqueueSeleniumJob, getSeleniumJobStatus } from "../queue/jobRunner"; import { getSeleniumJobStatus } from "../queue/jobRunner";
const router = Router(); const router = Router();
@@ -113,160 +113,6 @@ async function createOrUpdatePatientByInsuranceId(options: {
} }
} }
/**
* /eligibility-check
* - run selenium
* - if pdf created -> call extractor -> get name
* - create or update patient (by memberId)
* - attach PDF to patient (create pdf group/file)
* - return { patient, pdfFileId, extractedName ... }
*/
router.post(
"/eligibility-check",
async (req: Request, res: Response): Promise<any> => {
if (!req.body.data) {
return res
.status(400)
.json({ error: "Missing Insurance Eligibility data for selenium" });
}
if (!req.user || !req.user.id) {
return res.status(401).json({ error: "Unauthorized: user info missing" });
}
const insuranceEligibilityData =
typeof req.body.data === "string"
? JSON.parse(req.body.data)
: req.body.data;
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
req.user.id,
insuranceEligibilityData.insuranceSiteKey
);
if (!credentials) {
return res.status(404).json({
error:
"No insurance credentials found for this provider, Kindly Update this at Settings Page.",
});
}
const insuranceId = String(insuranceEligibilityData.memberId ?? "").trim();
if (!insuranceId) {
return res.status(400).json({ error: "Missing memberId" });
}
const enrichedData = {
...insuranceEligibilityData,
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
const job = await seleniumQueue.add("eligibility-check", {
jobType: "eligibility-check",
userId: req.user.id,
socketId: req.body.socketId,
enrichedPayload: enrichedData,
insuranceId,
formFirstName: insuranceEligibilityData.firstName,
formLastName: insuranceEligibilityData.lastName,
formDob: insuranceEligibilityData.dateOfBirth,
});
return res.json({ jobId: job.id, status: "queued" });
}
);
router.post(
"/eligibility-history-check",
async (req: Request, res: Response): Promise<any> => {
if (!req.body.data) {
return res.status(400).json({ error: "Missing Insurance Eligibility data for selenium" });
}
if (!req.user || !req.user.id) {
return res.status(401).json({ error: "Unauthorized: user info missing" });
}
const data =
typeof req.body.data === "string" ? JSON.parse(req.body.data) : req.body.data;
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, "MH");
if (!credentials) {
return res.status(404).json({
error: "No MassHealth credentials found. Please update them in Settings.",
});
}
const insuranceId = String(data.memberId ?? "").trim();
if (!insuranceId) {
return res.status(400).json({ error: "Missing memberId" });
}
const enrichedData = {
...data,
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
const jobId = enqueueSeleniumJob({
jobType: "mh-eligibility-history-check",
userId: req.user.id,
socketId: req.body.socketId,
enrichedPayload: enrichedData,
insuranceId,
formFirstName: data.firstName,
formLastName: data.lastName,
formDob: data.dateOfBirth,
});
return res.json({ jobId, status: "queued" });
}
);
router.post(
"/cmsp-eligibility-history-remaining-check",
async (req: Request, res: Response): Promise<any> => {
if (!req.body.data) {
return res.status(400).json({ error: "Missing data for selenium" });
}
if (!req.user || !req.user.id) {
return res.status(401).json({ error: "Unauthorized: user info missing" });
}
const data =
typeof req.body.data === "string" ? JSON.parse(req.body.data) : req.body.data;
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, "MH");
if (!credentials) {
return res.status(404).json({
error: "No MassHealth credentials found. Please update them in Settings.",
});
}
const insuranceId = String(data.memberId ?? "").trim();
if (!insuranceId) {
return res.status(400).json({ error: "Missing memberId" });
}
const enrichedData = {
...data,
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
const jobId = enqueueSeleniumJob({
jobType: "cmsp-eligibility-history-remaining-check",
userId: req.user.id,
socketId: req.body.socketId,
enrichedPayload: enrichedData,
insuranceId,
formFirstName: data.firstName,
formLastName: data.lastName,
formDob: data.dateOfBirth,
});
return res.json({ jobId, status: "queued" });
}
);
router.post( router.post(
"/claim-status-check", "/claim-status-check",
async (req: Request, res: Response): Promise<any> => { async (req: Request, res: Response): Promise<any> => {
@@ -3,6 +3,7 @@ import { storage } from "../storage";
import { enqueueSeleniumJob } from "../queue/jobRunner"; import { enqueueSeleniumJob } from "../queue/jobRunner";
import { forwardOtpToSeleniumUnitedDHClaimAgent } from "../services/seleniumUnitedDHClaimClient"; import { forwardOtpToSeleniumUnitedDHClaimAgent } from "../services/seleniumUnitedDHClaimClient";
import { io } from "../socket"; import { io } from "../socket";
import { prisma as db } from "@repo/db/client";
const router = Router(); const router = Router();
@@ -40,11 +41,14 @@ router.post("/uniteddh-claim", async (req: Request, res: Response): Promise<any>
}); });
} }
const seleniumSettings = await db.seleniumSettings.findUnique({ where: { userId: req.user.id } });
const enrichedPayload = { const enrichedPayload = {
claim: { claim: {
...claimData, ...claimData,
uniteddhUsername: credentials.username, uniteddhUsername: credentials.username,
uniteddhPassword: credentials.password, uniteddhPassword: credentials.password,
paymentGroupId: seleniumSettings?.paymentGroupId ?? "",
}, },
}; };
@@ -3,6 +3,7 @@ import { storage } from "../storage";
import { enqueueSeleniumJob } from "../queue/jobRunner"; import { enqueueSeleniumJob } from "../queue/jobRunner";
import { forwardOtpToSeleniumUnitedDHPreAuthAgent } from "../services/seleniumUnitedDHPreAuthClient"; import { forwardOtpToSeleniumUnitedDHPreAuthAgent } from "../services/seleniumUnitedDHPreAuthClient";
import { io } from "../socket"; import { io } from "../socket";
import { prisma as db } from "@repo/db/client";
const router = Router(); const router = Router();
@@ -40,11 +41,14 @@ router.post("/uniteddh-preauth", async (req: Request, res: Response): Promise<an
}); });
} }
const seleniumSettings = await db.seleniumSettings.findUnique({ where: { userId: req.user.id } });
const enrichedPayload = { const enrichedPayload = {
claim: { claim: {
...claimData, ...claimData,
uniteddhUsername: credentials.username, uniteddhUsername: credentials.username,
uniteddhPassword: credentials.password, uniteddhPassword: credentials.password,
paymentGroupId: seleniumSettings?.paymentGroupId ?? "",
}, },
}; };
@@ -3,6 +3,7 @@ import { storage } from "../storage";
import { forwardOtpToSeleniumUnitedSCOAgent } from "../services/seleniumUnitedSCOEligibilityClient"; import { forwardOtpToSeleniumUnitedSCOAgent } from "../services/seleniumUnitedSCOEligibilityClient";
import { io } from "../socket"; import { io } from "../socket";
import { enqueueSeleniumJob } from "../queue/jobRunner"; import { enqueueSeleniumJob } from "../queue/jobRunner";
import { prisma as db } from "@repo/db/client";
const router = Router(); const router = Router();
@@ -62,10 +63,13 @@ router.post(
}); });
} }
const seleniumSettings = await db.seleniumSettings.findUnique({ where: { userId: req.user.id } });
const enrichedData = { const enrichedData = {
...rawData, ...rawData,
unitedscoUsername: credentials.username, unitedscoUsername: credentials.username,
unitedscoPassword: credentials.password, unitedscoPassword: credentials.password,
paymentGroupId: seleniumSettings?.paymentGroupId ?? "",
}; };
const socketId: string | undefined = req.body.socketId; const socketId: string | undefined = req.body.socketId;
+92
View File
@@ -0,0 +1,92 @@
import express, { Request, Response } from "express";
import { storage } from "../storage";
const router = express.Router();
router.get("/templates", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const templates = await storage.getLabRxTemplates(userId);
return res.json(templates);
} catch (err) {
return res.status(500).json({ error: "Failed to fetch lab RX templates", details: String(err) });
}
});
router.post("/templates", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const { name, labName, labPhone, labFax, labAddress, labAccount, caseType, material, instructions } = req.body;
if (!name?.trim()) return res.status(400).json({ message: "Template name is required" });
const template = await storage.createLabRxTemplate(userId, {
name: name.trim(),
labName: labName?.trim() || undefined,
labPhone: labPhone?.trim() || undefined,
labFax: labFax?.trim() || undefined,
labAddress: labAddress?.trim() || undefined,
labAccount: labAccount?.trim() || undefined,
caseType: caseType?.trim() || undefined,
material: material?.trim() || undefined,
instructions: instructions?.trim() || undefined,
});
return res.status(201).json(template);
} catch (err) {
return res.status(500).json({ error: "Failed to create lab RX template", details: String(err) });
}
});
router.put("/templates/:id", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const id = Number(req.params.id);
if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });
const { name, labName, labPhone, labFax, labAddress, labAccount, caseType, material, instructions } = req.body;
if (name !== undefined && !name?.trim()) return res.status(400).json({ message: "Name cannot be empty" });
const template = await storage.updateLabRxTemplate(userId, id, {
name: name?.trim(),
labName: labName?.trim() || undefined,
labPhone: labPhone?.trim() || undefined,
labFax: labFax?.trim() || undefined,
labAddress: labAddress?.trim() || undefined,
labAccount: labAccount?.trim() || undefined,
caseType: caseType?.trim() || undefined,
material: material?.trim() || undefined,
instructions: instructions?.trim() || undefined,
});
return res.json(template);
} catch (err) {
return res.status(500).json({ error: "Failed to update lab RX template", details: String(err) });
}
});
router.post("/templates/reorder", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const { orderedIds } = req.body;
if (!Array.isArray(orderedIds)) return res.status(400).json({ message: "orderedIds must be an array" });
await storage.reorderLabRxTemplates(userId, orderedIds);
return res.status(204).send();
} catch (err) {
return res.status(500).json({ error: "Failed to reorder templates", details: String(err) });
}
});
router.delete("/templates/:id", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const id = Number(req.params.id);
if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });
const ok = await storage.deleteLabRxTemplate(userId, id);
if (!ok) return res.status(404).json({ message: "Template not found" });
return res.status(204).send();
} catch (err) {
return res.status(500).json({ error: "Failed to delete lab RX template", details: String(err) });
}
});
export default router;
@@ -0,0 +1,42 @@
import { Router, Request, Response } from "express";
import { prisma as db } from "@repo/db/client";
const router = Router();
// GET /api/selenium-settings
router.get("/", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const settings = await db.seleniumSettings.findUnique({ where: { userId } });
return res.status(200).json({ paymentGroupId: settings?.paymentGroupId ?? "" });
} catch (err) {
return res.status(500).json({ error: "Failed to fetch Selenium settings", details: String(err) });
}
});
// PUT /api/selenium-settings
router.put("/", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const { paymentGroupId } = req.body;
if (typeof paymentGroupId !== "string") {
return res.status(400).json({ message: "paymentGroupId must be a string" });
}
const settings = await db.seleniumSettings.upsert({
where: { userId },
update: { paymentGroupId: paymentGroupId.trim() },
create: { userId, paymentGroupId: paymentGroupId.trim() },
});
return res.status(200).json({ paymentGroupId: settings.paymentGroupId });
} catch (err) {
return res.status(500).json({ error: "Failed to save Selenium settings", details: String(err) });
}
});
export default router;
+2
View File
@@ -25,6 +25,7 @@ import { procedureTimeslotStorage } from "./procedure-timeslot-storage";
import { insuranceContactStorage } from "./insurance-contact-storage"; import { insuranceContactStorage } from "./insurance-contact-storage";
import { commissionsStorage } from "./commissions-storage"; import { commissionsStorage } from "./commissions-storage";
import { shoppingVendorStorage } from "./shopping-vendor-storage"; import { shoppingVendorStorage } from "./shopping-vendor-storage";
import { labRxStorage } from "./lab-rx-storage";
export const storage = { export const storage = {
@@ -53,6 +54,7 @@ export const storage = {
...insuranceContactStorage, ...insuranceContactStorage,
...commissionsStorage, ...commissionsStorage,
...shoppingVendorStorage, ...shoppingVendorStorage,
...labRxStorage,
}; };
@@ -0,0 +1,90 @@
import { prisma as db } from "@repo/db/client";
export type LabRxTemplateInput = {
name: string;
labName?: string;
labPhone?: string;
labFax?: string;
labAddress?: string;
labAccount?: string;
caseType?: string;
material?: string;
instructions?: string;
};
const DEFAULT_TEMPLATES: LabRxTemplateInput[] = [
{
name: "Highland Dental Crown",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Crown",
material: "Zirconia",
},
{
name: "Highland Dental Full Denture",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Full Denture",
},
{
name: "Highland Dental Partial Denture",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Partial Denture",
},
];
export const labRxStorage = {
async getLabRxTemplates(userId: number) {
const existing = await db.labRxTemplate.findMany({
where: { userId },
orderBy: { sortOrder: "asc" },
});
if (existing.length === 0) {
await db.labRxTemplate.createMany({
data: DEFAULT_TEMPLATES.map((t, index) => ({ userId, sortOrder: index, ...t })),
});
return db.labRxTemplate.findMany({
where: { userId },
orderBy: { sortOrder: "asc" },
});
}
return existing;
},
async reorderLabRxTemplates(userId: number, orderedIds: number[]) {
await db.$transaction(
orderedIds.map((id, index) =>
db.labRxTemplate.updateMany({
where: { id, userId },
data: { sortOrder: index },
})
)
);
},
async createLabRxTemplate(userId: number, data: LabRxTemplateInput) {
return db.labRxTemplate.create({
data: { userId, ...data },
});
},
async updateLabRxTemplate(userId: number, id: number, data: Partial<LabRxTemplateInput>) {
return db.labRxTemplate.update({
where: { id, userId },
data,
});
},
async deleteLabRxTemplate(userId: number, id: number) {
const existing = await db.labRxTemplate.findFirst({ where: { id, userId } });
if (!existing) return false;
await db.labRxTemplate.delete({ where: { id } });
return true;
},
};
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
<title>My Dental Office Management</title> <title>Wellspring Acupuncture Office Management</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+1
View File
@@ -68,6 +68,7 @@
"pdfjs-dist": "^3.11.174", "pdfjs-dist": "^3.11.174",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"react": "^19.1.0", "react": "^19.1.0",
"react-barcode": "^1.6.1",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-day-picker": "9.7.0", "react-day-picker": "9.7.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
+2
View File
@@ -29,6 +29,7 @@ const ReportsPage = lazy(() => import("./pages/reports-page"));
const CloudStoragePage = lazy(() => import("./pages/cloud-storage-page")); const CloudStoragePage = lazy(() => import("./pages/cloud-storage-page"));
const JobMonitorPage = lazy(() => import("./pages/job-monitor-page")); const JobMonitorPage = lazy(() => import("./pages/job-monitor-page"));
const ChartPage = lazy(() => import("./pages/chart-page")); const ChartPage = lazy(() => import("./pages/chart-page"));
const DentalEducationPage = lazy(() => import("./pages/dental-education-page"));
const AiInputAgentPage = lazy(() => import("./pages/ai-input-agent-page")); const AiInputAgentPage = lazy(() => import("./pages/ai-input-agent-page"));
const DentalShoppingSearchTagPage = lazy(() => import("./pages/dental-shopping-search-tag-page")); const DentalShoppingSearchTagPage = lazy(() => import("./pages/dental-shopping-search-tag-page"));
const DentalShoppingLoginInfoPage = lazy(() => import("./pages/dental-shopping-login-info-page")); const DentalShoppingLoginInfoPage = lazy(() => import("./pages/dental-shopping-login-info-page"));
@@ -65,6 +66,7 @@ function Router() {
/> />
<ProtectedRoute path="/reports" component={() => <ReportsPage />} /> <ProtectedRoute path="/reports" component={() => <ReportsPage />} />
<ProtectedRoute path="/cloud-storage" component={() => <CloudStoragePage />} /> <ProtectedRoute path="/cloud-storage" component={() => <CloudStoragePage />} />
<ProtectedRoute path="/dental-education" component={() => <DentalEducationPage />} />
<ProtectedRoute path="/ai-input-agent" component={() => <AiInputAgentPage />} /> <ProtectedRoute path="/ai-input-agent" component={() => <AiInputAgentPage />} />
<ProtectedRoute path="/dental-shopping/search-tag" component={() => <DentalShoppingSearchTagPage />} /> <ProtectedRoute path="/dental-shopping/search-tag" component={() => <DentalShoppingSearchTagPage />} />
<ProtectedRoute path="/dental-shopping/login-info" component={() => <DentalShoppingLoginInfoPage />} /> <ProtectedRoute path="/dental-shopping/login-info" component={() => <DentalShoppingLoginInfoPage />} />
@@ -0,0 +1,116 @@
import { useRef } from "react";
import Barcode from "react-barcode";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Download, Printer } from "lucide-react";
import { Patient } from "@repo/db/types";
interface BarcodeModalProps {
open: boolean;
onClose: () => void;
patient: Patient;
}
export function BarcodeModal({ open, onClose, patient }: BarcodeModalProps) {
const barcodeRef = useRef<HTMLDivElement>(null);
const patientLabel = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
const barcodeValue = patientLabel || "PATIENT";
const handlePrint = () => {
const svg = barcodeRef.current?.querySelector("svg");
if (!svg) return;
const svgData = new XMLSerializer().serializeToString(svg);
const printWindow = window.open("", "_blank", "width=400,height=300");
if (!printWindow) return;
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Barcode — ${patientLabel}</title>
<style>
body { margin: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: sans-serif; }
p { margin: 4px 0 0; font-size: 13px; color: #333; }
</style>
</head>
<body>
${svgData}
<p>${patientLabel}</p>
<script>window.onload = () => { window.print(); window.close(); }<\/script>
</body>
</html>
`);
printWindow.document.close();
};
const handleDownload = () => {
const svg = barcodeRef.current?.querySelector("svg");
if (!svg) return;
const canvas = document.createElement("canvas");
const svgData = new XMLSerializer().serializeToString(svg);
const img = new Image();
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(svgBlob);
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
const link = document.createElement("a");
link.download = `barcode-${patientLabel.replace(/\s+/g, "-")}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
};
img.src = url;
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-sm">
<DialogHeader>
<DialogTitle>Patient Barcode</DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center gap-3 py-4">
<div ref={barcodeRef}>
<Barcode
value={barcodeValue}
format="CODE128"
width={2}
height={80}
displayValue={true}
fontSize={13}
margin={10}
/>
</div>
</div>
<DialogFooter className="gap-2">
<Button variant="outline" onClick={handlePrint} className="gap-2">
<Printer className="h-4 w-4" />
Print
</Button>
<Button onClick={handleDownload} className="gap-2">
<Download className="h-4 w-4" />
Download PNG
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -1,258 +1,81 @@
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label"; import { PatientTable } from "@/components/patients/patient-table";
import { Textarea } from "@/components/ui/textarea"; import { BarcodeModal } from "@/components/chart/barcode-modal";
import { Badge } from "@/components/ui/badge"; import { LabRxModal } from "@/components/chart/lab-rx-modal";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Barcode, FileText } from "lucide-react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog";
import { Plus, Pencil, Trash2, Package } from "lucide-react";
const STATUS_COLORS = {
pending: "bg-yellow-100 text-yellow-700 border-yellow-200",
"in-lab": "bg-blue-100 text-blue-700 border-blue-200",
received: "bg-green-100 text-green-700 border-green-200",
delivered: "bg-gray-100 text-gray-600 border-gray-200",
cancelled: "bg-red-100 text-red-600 border-red-200",
};
const STATUS_LABELS = {
pending: "Pending",
"in-lab": "In Lab",
received: "Received",
delivered: "Delivered",
cancelled: "Cancelled",
};
const CASE_TYPES = [
"Crown – PFM",
"Crown – All Ceramic",
"Crown – Zirconia",
"Crown – Gold",
"Bridge – PFM",
"Bridge – Zirconia",
"Implant Crown",
"Implant Abutment",
"Veneer",
"Inlay / Onlay",
"Full Denture (Upper)",
"Full Denture (Lower)",
"Partial Denture",
"Night Guard",
"Bleaching Tray",
"Retainer",
"Diagnostic Model",
"Other",
];
const COMMON_LABS = [
"Dental Arts Lab",
"National Dentex",
"Glidewell Dental",
"Henry Schein Lab",
"Affordable Dentures Lab",
"Local Lab",
];
const SHADES = [
"A1", "A2", "A3", "A3.5", "A4",
"B1", "B2", "B3", "B4",
"C1", "C2", "C3", "C4",
"D2", "D3", "D4",
"BL1", "BL2", "BL3", "BL4",
"Custom",
];
let nextId = 1;
const newOrder = () => ({
id: nextId++,
orderDate: new Date().toISOString().substring(0, 10),
dueDate: "",
tooth: "",
caseType: "",
lab: "",
shade: "",
status: "pending",
rush: false,
notes: "",
trackingNumber: "",
});
export function LabManagementTab() { export function LabManagementTab() {
const [orders, setOrders] = useState([]); const [selectedPatient, setSelectedPatient] = useState(null);
const [dialogOpen, setDialogOpen] = useState(false); const [barcodeOpen, setBarcodeOpen] = useState(false);
const [editing, setEditing] = useState(newOrder()); const [labRxOpen, setLabRxOpen] = useState(false);
const openAdd = () => {
setEditing(newOrder());
setDialogOpen(true);
};
const openEdit = (order) => {
setEditing({ ...order });
setDialogOpen(true);
};
const handleSave = () => {
setOrders((prev) => {
const idx = prev.findIndex((o) => o.id === editing.id);
if (idx >= 0) {
const next = [...prev];
next[idx] = editing;
return next;
}
return [...prev, editing];
});
setDialogOpen(false);
};
const handleDelete = (id) => {
setOrders((prev) => prev.filter((o) => o.id !== id));
};
const pending = orders.filter((o) => o.status === "pending" || o.status === "in-lab").length;
const rush = orders.filter((o) => o.rush && o.status !== "delivered" && o.status !== "cancelled").length;
return (<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex gap-4 text-sm text-gray-600">
{pending > 0 && <span>Open orders: <strong>{pending}</strong></span>}
{rush > 0 && <span className="text-red-600">Rush: <strong>{rush}</strong></span>}
{orders.length === 0 && <span>No lab orders yet</span>}
</div>
<Button size="sm" onClick={openAdd} className="gap-1.5">
<Plus className="h-4 w-4"/>
New Lab Order
</Button>
</div>
<div className="border rounded-lg overflow-hidden"> return (
<Table> <div className="space-y-6">
<TableHeader> <Card>
<TableRow className="bg-gray-50"> <CardHeader>
<TableHead className="w-24">Order Date</TableHead> <CardTitle>Lab Actions</CardTitle>
<TableHead className="w-16">Tooth</TableHead> <CardDescription>
<TableHead>Case Type</TableHead> {selectedPatient
<TableHead>Lab</TableHead> ? `Selected patient: ${selectedPatient.firstName} ${selectedPatient.lastName}`
<TableHead className="w-16 text-center">Shade</TableHead> : "Select a patient below to get started."}
<TableHead className="w-24">Due</TableHead> </CardDescription>
<TableHead className="w-28">Status</TableHead> </CardHeader>
<TableHead className="w-20 text-right">Actions</TableHead> <CardContent>
</TableRow> <div className="flex gap-4">
</TableHeader> <Button
<TableBody> variant="outline"
{orders.length === 0 ? (<TableRow> className="gap-2"
<TableCell colSpan={8} className="text-center text-gray-400 py-10"> disabled={!selectedPatient}
<div className="flex flex-col items-center gap-2"> onClick={() => setBarcodeOpen(true)}
<Package className="h-8 w-8 text-gray-300"/> >
<span>No lab orders yet. Click "New Lab Order" to add one.</span> <Barcode className="h-4 w-4" />
</div> Create a Barcode
</TableCell> </Button>
</TableRow>) : (orders.map((order) => (<TableRow key={order.id} className={order.rush ? "bg-red-50/40" : ""}> <Button
<TableCell className="text-sm">{order.orderDate}</TableCell> variant="outline"
<TableCell className="font-mono text-sm">{order.tooth || "—"}</TableCell> className="gap-2"
<TableCell> disabled={!selectedPatient}
<div className="flex items-center gap-1.5"> onClick={() => setLabRxOpen(true)}
<span className="text-sm font-medium">{order.caseType}</span> >
{order.rush && (<Badge className="text-[10px] bg-red-100 text-red-600 border-red-200 px-1 py-0" variant="outline"> <FileText className="h-4 w-4" />
RUSH Lab RX
</Badge>)} </Button>
</div>
{order.notes && (<p className="text-xs text-gray-400 truncate max-w-xs mt-0.5">{order.notes}</p>)}
</TableCell>
<TableCell className="text-sm text-gray-600">{order.lab || "—"}</TableCell>
<TableCell className="text-center text-sm">{order.shade || "—"}</TableCell>
<TableCell className="text-sm text-gray-600">{order.dueDate || "—"}</TableCell>
<TableCell>
<Badge className={`text-xs border ${STATUS_COLORS[order.status]}`} variant="outline">
{STATUS_LABELS[order.status]}
</Badge>
</TableCell>
<TableCell>
<div className="flex justify-end gap-1">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => openEdit(order)}>
<Pencil className="h-3.5 w-3.5"/>
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-red-500 hover:text-red-600 hover:bg-red-50" onClick={() => handleDelete(order.id)}>
<Trash2 className="h-3.5 w-3.5"/>
</Button>
</div>
</TableCell>
</TableRow>)))}
</TableBody>
</Table>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Lab Order</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-3 py-2">
<div className="space-y-1">
<Label className="text-xs">Order Date</Label>
<Input type="date" value={editing.orderDate} onChange={(e) => setEditing((d) => ({ ...d, orderDate: e.target.value }))} className="h-9 text-sm"/>
</div>
<div className="space-y-1">
<Label className="text-xs">Due Date</Label>
<Input type="date" value={editing.dueDate} onChange={(e) => setEditing((d) => ({ ...d, dueDate: e.target.value }))} className="h-9 text-sm"/>
</div>
<div className="space-y-1">
<Label className="text-xs">Tooth #</Label>
<Input placeholder="e.g. 14" value={editing.tooth} onChange={(e) => setEditing((d) => ({ ...d, tooth: e.target.value }))} className="h-9 text-sm"/>
</div>
<div className="space-y-1">
<Label className="text-xs">Shade</Label>
<Select value={editing.shade} onValueChange={(v) => setEditing((d) => ({ ...d, shade: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select..."/>
</SelectTrigger>
<SelectContent>
{SHADES.map((s) => <SelectItem key={s} value={s}>{s}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Case Type</Label>
<Select value={editing.caseType} onValueChange={(v) => setEditing((d) => ({ ...d, caseType: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select case type..."/>
</SelectTrigger>
<SelectContent>
{CASE_TYPES.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Lab</Label>
<Select value={editing.lab} onValueChange={(v) => setEditing((d) => ({ ...d, lab: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select lab..."/>
</SelectTrigger>
<SelectContent>
{COMMON_LABS.map((l) => <SelectItem key={l} value={l}>{l}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs">Status</Label>
<Select value={editing.status} onValueChange={(v) => setEditing((d) => ({ ...d, status: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.keys(STATUS_LABELS).map((s) => (<SelectItem key={s} value={s}>{STATUS_LABELS[s]}</SelectItem>))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs">Tracking #</Label>
<Input placeholder="Optional" value={editing.trackingNumber} onChange={(e) => setEditing((d) => ({ ...d, trackingNumber: e.target.value }))} className="h-9 text-sm"/>
</div>
<div className="col-span-2 flex items-center gap-2">
<input type="checkbox" id="rush" checked={editing.rush} onChange={(e) => setEditing((d) => ({ ...d, rush: e.target.checked }))} className="h-4 w-4 rounded border-gray-300"/>
<Label htmlFor="rush" className="text-sm cursor-pointer text-red-600 font-medium">
Rush Order
</Label>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Notes</Label>
<Textarea placeholder="Special instructions, shade details..." value={editing.notes} onChange={(e) => setEditing((d) => ({ ...d, notes: e.target.value }))} className="text-sm resize-none" rows={2}/>
</div>
</div> </div>
<DialogFooter> </CardContent>
<Button variant="outline" onClick={() => setDialogOpen(false)}>Cancel</Button> </Card>
<Button onClick={handleSave} disabled={!editing.caseType}>Save</Button>
</DialogFooter> <Card>
</DialogContent> <CardHeader>
</Dialog> <CardTitle>Patient Records</CardTitle>
</div>); <CardDescription>Select a patient by clicking the checkbox.</CardDescription>
</CardHeader>
<CardContent>
<PatientTable
allowView={true}
allowDelete={false}
allowCheckbox={true}
allowEdit={false}
onSelectPatient={setSelectedPatient}
/>
</CardContent>
</Card>
{selectedPatient && (
<>
<BarcodeModal
open={barcodeOpen}
onClose={() => setBarcodeOpen(false)}
patient={selectedPatient}
/>
<LabRxModal
open={labRxOpen}
onClose={() => setLabRxOpen(false)}
patient={selectedPatient}
/>
</>
)}
</div>
);
} }
@@ -1,364 +1,84 @@
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label"; import { PatientTable } from "@/components/patients/patient-table";
import { Textarea } from "@/components/ui/textarea"; import { BarcodeModal } from "@/components/chart/barcode-modal";
import { Badge } from "@/components/ui/badge"; import { LabRxModal } from "@/components/chart/lab-rx-modal";
import { import { Barcode, FileText } from "lucide-react";
Select, import { Patient } from "@repo/db/types";
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Plus, Pencil, Trash2, Package } from "lucide-react";
type LabStatus = "pending" | "in-lab" | "received" | "delivered" | "cancelled";
interface LabOrder {
id: number;
orderDate: string;
dueDate: string;
tooth: string;
caseType: string;
lab: string;
shade: string;
status: LabStatus;
rush: boolean;
notes: string;
trackingNumber: string;
}
const STATUS_COLORS: Record<LabStatus, string> = {
pending: "bg-yellow-100 text-yellow-700 border-yellow-200",
"in-lab": "bg-blue-100 text-blue-700 border-blue-200",
received: "bg-green-100 text-green-700 border-green-200",
delivered: "bg-gray-100 text-gray-600 border-gray-200",
cancelled: "bg-red-100 text-red-600 border-red-200",
};
const STATUS_LABELS: Record<LabStatus, string> = {
pending: "Pending",
"in-lab": "In Lab",
received: "Received",
delivered: "Delivered",
cancelled: "Cancelled",
};
const CASE_TYPES = [
"Crown – PFM",
"Crown – All Ceramic",
"Crown – Zirconia",
"Crown – Gold",
"Bridge – PFM",
"Bridge – Zirconia",
"Implant Crown",
"Implant Abutment",
"Veneer",
"Inlay / Onlay",
"Full Denture (Upper)",
"Full Denture (Lower)",
"Partial Denture",
"Night Guard",
"Bleaching Tray",
"Retainer",
"Diagnostic Model",
"Other",
];
const COMMON_LABS = [
"Dental Arts Lab",
"National Dentex",
"Glidewell Dental",
"Henry Schein Lab",
"Affordable Dentures Lab",
"Local Lab",
];
const SHADES = [
"A1", "A2", "A3", "A3.5", "A4",
"B1", "B2", "B3", "B4",
"C1", "C2", "C3", "C4",
"D2", "D3", "D4",
"BL1", "BL2", "BL3", "BL4",
"Custom",
];
let nextId = 1;
const newOrder = (): LabOrder => ({
id: nextId++,
orderDate: new Date().toISOString().substring(0, 10),
dueDate: "",
tooth: "",
caseType: "",
lab: "",
shade: "",
status: "pending",
rush: false,
notes: "",
trackingNumber: "",
});
export function LabManagementTab() { export function LabManagementTab() {
const [orders, setOrders] = useState<LabOrder[]>([]); const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
const [dialogOpen, setDialogOpen] = useState(false); const [barcodeOpen, setBarcodeOpen] = useState(false);
const [editing, setEditing] = useState<LabOrder>(newOrder()); const [labRxOpen, setLabRxOpen] = useState(false);
const openAdd = () => {
setEditing(newOrder());
setDialogOpen(true);
};
const openEdit = (order: LabOrder) => {
setEditing({ ...order });
setDialogOpen(true);
};
const handleSave = () => {
setOrders((prev) => {
const idx = prev.findIndex((o) => o.id === editing.id);
if (idx >= 0) {
const next = [...prev];
next[idx] = editing;
return next;
}
return [...prev, editing];
});
setDialogOpen(false);
};
const handleDelete = (id: number) => {
setOrders((prev) => prev.filter((o) => o.id !== id));
};
const pending = orders.filter((o) => o.status === "pending" || o.status === "in-lab").length;
const rush = orders.filter((o) => o.rush && o.status !== "delivered" && o.status !== "cancelled").length;
return ( return (
<div className="space-y-4"> <div className="space-y-6">
<div className="flex items-center justify-between"> {/* Action buttons */}
<div className="flex gap-4 text-sm text-gray-600"> <Card>
{pending > 0 && <span>Open orders: <strong>{pending}</strong></span>} <CardHeader>
{rush > 0 && <span className="text-red-600">Rush: <strong>{rush}</strong></span>} <CardTitle>Lab Actions</CardTitle>
{orders.length === 0 && <span>No lab orders yet</span>} <CardDescription>
</div> {selectedPatient
<Button size="sm" onClick={openAdd} className="gap-1.5"> ? `Selected patient: ${selectedPatient.firstName} ${selectedPatient.lastName}`
<Plus className="h-4 w-4" /> : "Select a patient below to get started."}
New Lab Order </CardDescription>
</Button> </CardHeader>
</div> <CardContent>
<div className="flex gap-4">
<div className="border rounded-lg overflow-hidden"> <Button
<Table> variant="outline"
<TableHeader> className="gap-2"
<TableRow className="bg-gray-50"> disabled={!selectedPatient}
<TableHead className="w-24">Order Date</TableHead> onClick={() => setBarcodeOpen(true)}
<TableHead className="w-16">Tooth</TableHead> >
<TableHead>Case Type</TableHead> <Barcode className="h-4 w-4" />
<TableHead>Lab</TableHead> Create a Barcode
<TableHead className="w-16 text-center">Shade</TableHead> </Button>
<TableHead className="w-24">Due</TableHead> <Button
<TableHead className="w-28">Status</TableHead> variant="outline"
<TableHead className="w-20 text-right">Actions</TableHead> className="gap-2"
</TableRow> disabled={!selectedPatient}
</TableHeader> onClick={() => setLabRxOpen(true)}
<TableBody> >
{orders.length === 0 ? ( <FileText className="h-4 w-4" />
<TableRow> Lab RX
<TableCell colSpan={8} className="text-center text-gray-400 py-10"> </Button>
<div className="flex flex-col items-center gap-2">
<Package className="h-8 w-8 text-gray-300" />
<span>No lab orders yet. Click "New Lab Order" to add one.</span>
</div>
</TableCell>
</TableRow>
) : (
orders.map((order) => (
<TableRow key={order.id} className={order.rush ? "bg-red-50/40" : ""}>
<TableCell className="text-sm">{order.orderDate}</TableCell>
<TableCell className="font-mono text-sm">{order.tooth || "—"}</TableCell>
<TableCell>
<div className="flex items-center gap-1.5">
<span className="text-sm font-medium">{order.caseType}</span>
{order.rush && (
<Badge className="text-[10px] bg-red-100 text-red-600 border-red-200 px-1 py-0" variant="outline">
RUSH
</Badge>
)}
</div>
{order.notes && (
<p className="text-xs text-gray-400 truncate max-w-xs mt-0.5">{order.notes}</p>
)}
</TableCell>
<TableCell className="text-sm text-gray-600">{order.lab || "—"}</TableCell>
<TableCell className="text-center text-sm">{order.shade || "—"}</TableCell>
<TableCell className="text-sm text-gray-600">{order.dueDate || "—"}</TableCell>
<TableCell>
<Badge className={`text-xs border ${STATUS_COLORS[order.status]}`} variant="outline">
{STATUS_LABELS[order.status]}
</Badge>
</TableCell>
<TableCell>
<div className="flex justify-end gap-1">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => openEdit(order)}>
<Pencil className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-red-500 hover:text-red-600 hover:bg-red-50" onClick={() => handleDelete(order.id)}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Lab Order</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-3 py-2">
<div className="space-y-1">
<Label className="text-xs">Order Date</Label>
<Input
type="date"
value={editing.orderDate}
onChange={(e) => setEditing((d) => ({ ...d, orderDate: e.target.value }))}
className="h-9 text-sm"
/>
</div>
<div className="space-y-1">
<Label className="text-xs">Due Date</Label>
<Input
type="date"
value={editing.dueDate}
onChange={(e) => setEditing((d) => ({ ...d, dueDate: e.target.value }))}
className="h-9 text-sm"
/>
</div>
<div className="space-y-1">
<Label className="text-xs">Tooth #</Label>
<Input
placeholder="e.g. 14"
value={editing.tooth}
onChange={(e) => setEditing((d) => ({ ...d, tooth: e.target.value }))}
className="h-9 text-sm"
/>
</div>
<div className="space-y-1">
<Label className="text-xs">Shade</Label>
<Select
value={editing.shade}
onValueChange={(v) => setEditing((d) => ({ ...d, shade: v }))}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select..." />
</SelectTrigger>
<SelectContent>
{SHADES.map((s) => <SelectItem key={s} value={s}>{s}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Case Type</Label>
<Select
value={editing.caseType}
onValueChange={(v) => setEditing((d) => ({ ...d, caseType: v }))}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select case type..." />
</SelectTrigger>
<SelectContent>
{CASE_TYPES.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Lab</Label>
<Select
value={editing.lab}
onValueChange={(v) => setEditing((d) => ({ ...d, lab: v }))}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select lab..." />
</SelectTrigger>
<SelectContent>
{COMMON_LABS.map((l) => <SelectItem key={l} value={l}>{l}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs">Status</Label>
<Select
value={editing.status}
onValueChange={(v) => setEditing((d) => ({ ...d, status: v as LabStatus }))}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(STATUS_LABELS) as LabStatus[]).map((s) => (
<SelectItem key={s} value={s}>{STATUS_LABELS[s]}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs">Tracking #</Label>
<Input
placeholder="Optional"
value={editing.trackingNumber}
onChange={(e) => setEditing((d) => ({ ...d, trackingNumber: e.target.value }))}
className="h-9 text-sm"
/>
</div>
<div className="col-span-2 flex items-center gap-2">
<input
type="checkbox"
id="rush"
checked={editing.rush}
onChange={(e) => setEditing((d) => ({ ...d, rush: e.target.checked }))}
className="h-4 w-4 rounded border-gray-300"
/>
<Label htmlFor="rush" className="text-sm cursor-pointer text-red-600 font-medium">
Rush Order
</Label>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs">Notes</Label>
<Textarea
placeholder="Special instructions, shade details..."
value={editing.notes}
onChange={(e) => setEditing((d) => ({ ...d, notes: e.target.value }))}
className="text-sm resize-none"
rows={2}
/>
</div>
</div> </div>
<DialogFooter> </CardContent>
<Button variant="outline" onClick={() => setDialogOpen(false)}>Cancel</Button> </Card>
<Button onClick={handleSave} disabled={!editing.caseType}>Save</Button>
</DialogFooter> {/* Patient search / selection */}
</DialogContent> <Card>
</Dialog> <CardHeader>
<CardTitle>Patient Records</CardTitle>
<CardDescription>Select a patient by clicking the checkbox.</CardDescription>
</CardHeader>
<CardContent>
<PatientTable
allowView={true}
allowDelete={false}
allowCheckbox={true}
allowEdit={false}
onSelectPatient={setSelectedPatient}
/>
</CardContent>
</Card>
{selectedPatient && (
<>
<BarcodeModal
open={barcodeOpen}
onClose={() => setBarcodeOpen(false)}
patient={selectedPatient}
/>
<LabRxModal
open={labRxOpen}
onClose={() => setLabRxOpen(false)}
patient={selectedPatient}
/>
</>
)}
</div> </div>
); );
} }
@@ -0,0 +1,626 @@
import { useState, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus, Pencil, Trash2, Printer, X, Check, FlaskConical, ChevronUp, ChevronDown } from "lucide-react";
const EMPTY_TEMPLATE = {
name: "", labName: "", labPhone: "", labFax: "",
labAddress: "", labAccount: "", caseType: "", material: "", instructions: "Please make\nThank you!",
};
const EMPTY_RX = { toothNumbers: "", shade: "", dueDate: "", doctorName: "", age: "", officeName: "", additionalNotes: "Please make\nThank you!" };
const calcAge = (dob) => {
if (!dob) return "";
const birth = new Date(dob);
const today = new Date();
let age = today.getFullYear() - birth.getFullYear();
const m = today.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
return String(age);
};
const CASE_TYPES = ["Crown", "Bridge", "Veneer", "Inlay/Onlay", "Full Denture", "Partial Denture", "Night Guard", "Retainer", "Implant Crown", "Other"];
const MATERIALS = ["Zirconia", "PFM (Porcelain-Fused-to-Metal)", "E-Max", "Acrylic", "Cast Metal", "Composite", "PMMA", "Other"];
export function LabRxModal({ open, onClose, patient }) {
const { toast } = useToast();
const queryClient = useQueryClient();
const printRef = useRef(null);
const [rxTemplate, setRxTemplate] = useState(null);
const [rx, setRx] = useState(EMPTY_RX);
const [renamingId, setRenamingId] = useState(null);
const [renameValue, setRenameValue] = useState("");
const [showTemplateForm, setShowTemplateForm] = useState(false);
const [editingTemplateId, setEditingTemplateId] = useState(null);
const [templateForm, setTemplateForm] = useState(EMPTY_TEMPLATE);
const { data: providers = [] } = useQuery({
queryKey: ["/api/npiProviders"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/npiProviders");
if (!res.ok) throw new Error("Failed to fetch providers");
return res.json();
},
enabled: open,
});
const { data: officeContact } = useQuery({
queryKey: ["/api/office-contact"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/office-contact");
if (!res.ok) return null;
return res.json();
},
enabled: open,
});
const { data: templates = [], isLoading } = useQuery({
queryKey: ["/api/lab-rx/templates"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/lab-rx/templates");
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
},
enabled: open,
});
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["/api/lab-rx/templates"] });
const createMutation = useMutation({
mutationFn: async (data) => {
const res = await apiRequest("POST", "/api/lab-rx/templates", data);
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to save"); }
return res.json();
},
onSuccess: () => { invalidate(); setShowTemplateForm(false); setTemplateForm(EMPTY_TEMPLATE); toast({ title: "Template saved" }); },
onError: (e) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const updateMutation = useMutation({
mutationFn: async ({ id, data }) => {
const res = await apiRequest("PUT", `/api/lab-rx/templates/${id}`, data);
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to update"); }
return res.json();
},
onSuccess: () => { invalidate(); setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); setShowTemplateForm(false); toast({ title: "Template updated" }); },
onError: (e) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const renameMutation = useMutation({
mutationFn: async ({ id, name }) => {
const res = await apiRequest("PUT", `/api/lab-rx/templates/${id}`, { name });
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to rename"); }
return res.json();
},
onSuccess: () => { invalidate(); setRenamingId(null); },
onError: (e) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const deleteMutation = useMutation({
mutationFn: async (id) => {
const res = await apiRequest("DELETE", `/api/lab-rx/templates/${id}`);
if (!res.ok) throw new Error("Failed to delete");
},
onSuccess: () => { invalidate(); toast({ title: "Template deleted" }); },
onError: (e) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const reorderMutation = useMutation({
mutationFn: async (orderedIds) => {
const res = await apiRequest("POST", "/api/lab-rx/templates/reorder", { orderedIds });
if (!res.ok) throw new Error("Failed to reorder");
},
onSuccess: () => invalidate(),
onError: (e) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const moveTemplate = (index, direction) => {
const swapIndex = direction === "up" ? index - 1 : index + 1;
const reordered = [...templates];
const a = reordered[index];
const b = reordered[swapIndex];
reordered[index] = b;
reordered[swapIndex] = a;
queryClient.setQueryData(["/api/lab-rx/templates"], reordered);
reorderMutation.mutate(reordered.map((t) => t.id));
};
const openRx = (t) => {
if (renamingId !== null) return;
setRxTemplate(t);
setRx({ ...EMPTY_RX, doctorName: providers[0]?.providerName ?? "", age: calcAge(patient.dateOfBirth), officeName: officeContact?.officeName ?? "" });
};
const startRename = (e, t) => {
e.stopPropagation();
setRenamingId(t.id);
setRenameValue(t.name);
};
const commitRename = () => {
if (!renameValue.trim()) { toast({ title: "Name cannot be empty", variant: "destructive" }); return; }
if (renamingId !== null) renameMutation.mutate({ id: renamingId, name: renameValue.trim() });
};
const cancelRename = () => setRenamingId(null);
const openAddTemplate = () => { setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); setShowTemplateForm(true); };
const openEditTemplate = (t) => {
setEditingTemplateId(t.id);
setTemplateForm({ name: t.name, labName: t.labName ?? "", labPhone: t.labPhone ?? "", labFax: t.labFax ?? "", labAddress: t.labAddress ?? "", labAccount: t.labAccount ?? "", caseType: t.caseType ?? "", material: t.material ?? "", instructions: t.instructions ?? "" });
setShowTemplateForm(true);
};
const handleSaveTemplate = () => {
if (!templateForm.name.trim()) { toast({ title: "Template name is required", variant: "destructive" }); return; }
if (editingTemplateId !== null) updateMutation.mutate({ id: editingTemplateId, data: templateForm });
else createMutation.mutate(templateForm);
};
const isSaving = createMutation.isPending || updateMutation.isPending;
const handlePrint = () => {
const content = printRef.current;
if (!content || !rxTemplate) return;
const win = window.open("", "_blank", "width=800,height=900");
if (!win) return;
win.document.write(`<!DOCTYPE html><html><head><title>Lab RX</title>
<style>
body{font-family:Arial,sans-serif;margin:0;padding:24px;font-size:13px;color:#111}
h1{font-size:22px;margin:0 0 4px;text-align:center}
.subtitle{text-align:center;color:#555;font-size:12px;margin-bottom:12px}
hr{border:none;border-top:2px solid #000;margin:10px 0}
.section{margin-bottom:14px}
.section-title{font-weight:bold;font-size:11px;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid #bbb;margin-bottom:8px;padding-bottom:3px;color:#333}
.row{display:flex;gap:24px;margin-bottom:6px}
.field{flex:1}
.field label{display:block;font-size:10px;color:#777;margin-bottom:1px}
.field span{display:block;border-bottom:1px solid #aaa;padding-bottom:2px;min-height:18px;font-size:13px}
.instructions-box{border:1px solid #bbb;padding:8px;min-height:64px;white-space:pre-wrap;font-size:12px}
.sig-area{display:flex;gap:48px;margin-top:32px}
.sig-line{border-top:1px solid #000;padding-top:3px;font-size:10px;color:#777;min-width:180px}
@media print{body{margin:0}}
</style>
</head><body>${content.innerHTML}</body></html>`);
win.document.close();
win.focus();
setTimeout(() => { win.print(); win.close(); }, 300);
};
const patientName = `${patient.firstName} ${patient.lastName}`;
const today = new Date().toLocaleDateString("en-US");
return (
<>
{/* Main modal */}
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FlaskConical className="h-5 w-5" />
Lab RX — {patientName}
</DialogTitle>
</DialogHeader>
<Tabs defaultValue="rx">
<TabsList className="mb-4">
<TabsTrigger value="rx">Create Lab RX</TabsTrigger>
<TabsTrigger value="templates">Manage Templates</TabsTrigger>
</TabsList>
{/* Tab 1: pick template */}
<TabsContent value="rx" className="space-y-4">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wide">
Click a template to create a Lab RX
</p>
{isLoading ? (
<p className="text-sm text-gray-400">Loading templates…</p>
) : templates.length === 0 ? (
<p className="text-sm text-gray-400">No templates yet. Go to "Manage Templates" to add one.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{templates.map((t) => (
<div
key={t.id}
onClick={() => openRx(t)}
className="group relative border rounded-lg p-3 bg-white cursor-pointer hover:border-blue-400 hover:shadow-sm transition-all"
>
{renamingId === t.id ? (
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<Input
autoFocus
value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") commitRename(); if (e.key === "Escape") cancelRename(); }}
className="h-7 text-sm font-medium"
/>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 text-green-600" onClick={commitRename} disabled={renameMutation.isPending}>
<Check className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0" onClick={cancelRename}>
<X className="h-3.5 w-3.5" />
</Button>
</div>
) : (
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="font-semibold text-sm text-gray-900 truncate">{t.name}</p>
{t.labName && <p className="text-xs text-gray-500 mt-0.5">{t.labName}</p>}
<div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-1 text-xs text-gray-400">
{t.caseType && <span>{t.caseType}</span>}
{t.material && <span>{t.material}</span>}
</div>
</div>
<button
title="Rename template"
onClick={(e) => startRename(e, t)}
className="shrink-0 p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-gray-100 transition-opacity"
>
<Pencil className="h-3.5 w-3.5 text-gray-400" />
</button>
</div>
)}
</div>
))}
</div>
)}
</TabsContent>
{/* Tab 2: manage templates */}
<TabsContent value="templates" className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">{templates.length} template{templates.length !== 1 ? "s" : ""}</p>
<Button size="sm" onClick={openAddTemplate} className="gap-1.5">
<Plus className="h-4 w-4" /> Add Template
</Button>
</div>
{showTemplateForm && (
<div className="border rounded-lg overflow-hidden">
<div className="bg-gray-100 border-b px-4 py-2.5">
<p className="text-sm font-semibold text-gray-800">
{editingTemplateId !== null ? "Edit Template" : "New Template"}
</p>
</div>
<div className="p-4 space-y-5 bg-white">
{/* Template Name section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-blue-600">Template Name</p>
<div className="flex-1 h-px bg-blue-100" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Name <span className="text-red-500">*</span></Label>
<Input
placeholder="e.g. Glidewell Crown, Argen Bridge…"
value={templateForm.name}
onChange={(e) => setTemplateForm((f) => ({ ...f, name: e.target.value }))}
className="h-9 text-sm"
/>
<p className="text-xs text-gray-400">This name appears on the template picker button.</p>
</div>
</div>
{/* Lab Information section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Lab Information</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Lab Name</Label>
<Input placeholder="e.g. Glidewell Dental" value={templateForm.labName} onChange={(e) => setTemplateForm((f) => ({ ...f, labName: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Account #</Label>
<Input placeholder="e.g. 123456" value={templateForm.labAccount} onChange={(e) => setTemplateForm((f) => ({ ...f, labAccount: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Phone</Label>
<Input placeholder="(800) 000-0000" value={templateForm.labPhone} onChange={(e) => setTemplateForm((f) => ({ ...f, labPhone: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Fax</Label>
<Input placeholder="(800) 000-0001" value={templateForm.labFax} onChange={(e) => setTemplateForm((f) => ({ ...f, labFax: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Address</Label>
<Input placeholder="123 Lab St, City, State 00000" value={templateForm.labAddress} onChange={(e) => setTemplateForm((f) => ({ ...f, labAddress: e.target.value }))} className="h-9 text-sm" />
</div>
</div>
</div>
{/* Default Case Details section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Default Case Details</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Case Type</Label>
<Select value={templateForm.caseType} onValueChange={(v) => setTemplateForm((f) => ({ ...f, caseType: v }))}>
<SelectTrigger className="h-9 text-sm"><SelectValue placeholder="Select…" /></SelectTrigger>
<SelectContent>{CASE_TYPES.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Material</Label>
<Select value={templateForm.material} onValueChange={(v) => setTemplateForm((f) => ({ ...f, material: v }))}>
<SelectTrigger className="h-9 text-sm"><SelectValue placeholder="Select…" /></SelectTrigger>
<SelectContent>{MATERIALS.map((m) => <SelectItem key={m} value={m}>{m}</SelectItem>)}</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Instructions</Label>
<Textarea placeholder="Standard instructions for this lab…" value={templateForm.instructions} onChange={(e) => setTemplateForm((f) => ({ ...f, instructions: e.target.value }))} className="text-sm resize-none" rows={3} />
</div>
</div>
</div>
</div>
<div className="bg-gray-50 border-t px-4 py-3 flex gap-2">
<Button size="sm" onClick={handleSaveTemplate} disabled={isSaving}>
<Check className="h-3.5 w-3.5 mr-1" />{isSaving ? "Saving…" : "Save Template"}
</Button>
<Button size="sm" variant="outline" onClick={() => { setShowTemplateForm(false); setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); }} disabled={isSaving}>
<X className="h-3.5 w-3.5 mr-1" /> Cancel
</Button>
</div>
</div>
)}
{isLoading ? (
<p className="text-sm text-gray-400 py-4 text-center">Loading…</p>
) : templates.length === 0 ? (
<p className="text-sm text-gray-400 py-8 text-center">No templates yet. Click "Add Template" to create one.</p>
) : (
<div className="space-y-2">
{templates.map((t, index) => (
<div key={t.id} className="border rounded-lg p-3 bg-white flex items-center gap-2">
<div className="flex flex-col shrink-0">
<Button
variant="ghost" size="icon" className="h-6 w-6 text-gray-400 hover:text-gray-700 disabled:opacity-20"
disabled={index === 0 || reorderMutation.isPending}
onClick={() => moveTemplate(index, "up")}
>
<ChevronUp className="h-4 w-4" />
</Button>
<Button
variant="ghost" size="icon" className="h-6 w-6 text-gray-400 hover:text-gray-700 disabled:opacity-20"
disabled={index === templates.length - 1 || reorderMutation.isPending}
onClick={() => moveTemplate(index, "down")}
>
<ChevronDown className="h-4 w-4" />
</Button>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-sm text-gray-900">{t.name}</p>
{t.labName && <p className="text-xs text-gray-500">{t.labName}</p>}
<div className="flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-gray-400 mt-0.5">
{t.caseType && <span>Case: {t.caseType}</span>}
{t.material && <span>Material: {t.material}</span>}
{t.labPhone && <span>Ph: {t.labPhone}</span>}
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => openEditTemplate(t)}>
<Pencil className="h-3.5 w-3.5 text-gray-500" />
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => deleteMutation.mutate(t.id)} disabled={deleteMutation.isPending}>
<Trash2 className="h-3.5 w-3.5 text-red-500" />
</Button>
</div>
</div>
))}
</div>
)}
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
{/* RX popup */}
<Dialog open={!!rxTemplate} onOpenChange={(v) => { if (!v) setRxTemplate(null); }}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Printer className="h-5 w-5" />
Lab RX — {rxTemplate?.name}
</DialogTitle>
</DialogHeader>
{rxTemplate && (
<>
<div className="space-y-5 py-2">
{/* Tooth Number section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-blue-600">Tooth Number</p>
<div className="flex-1 h-px bg-blue-100" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Tooth Number(s)</Label>
<Input placeholder="e.g. 3, 14, 19" value={rx.toothNumbers} onChange={(e) => setRx((r) => ({ ...r, toothNumbers: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Shade</Label>
<Input placeholder="e.g. A2, B1" value={rx.shade} onChange={(e) => setRx((r) => ({ ...r, shade: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Age</Label>
<Input placeholder="e.g. 72" value={rx.age} onChange={(e) => setRx((r) => ({ ...r, age: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Due Date</Label>
<Input type="date" value={rx.dueDate} onChange={(e) => setRx((r) => ({ ...r, dueDate: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Doctor's Name</Label>
<Select value={rx.doctorName} onValueChange={(v) => setRx((r) => ({ ...r, doctorName: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select provider…" />
</SelectTrigger>
<SelectContent>
{providers.map((p) => (
<SelectItem key={p.id} value={p.providerName}>{p.providerName}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Dental Office Name</Label>
<Input placeholder="e.g. Summit Dental Care" value={rx.officeName} onChange={(e) => setRx((r) => ({ ...r, officeName: e.target.value }))} className="h-9 text-sm" />
</div>
</div>
</div>
{/* Instructions section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Instructions</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
{rxTemplate.instructions && (
<div className="rounded-md bg-gray-50 border px-3 py-2 text-xs text-gray-600 whitespace-pre-wrap">
<span className="font-medium text-gray-400 block mb-1">From template</span>
{rxTemplate.instructions}
</div>
)}
<div className="space-y-1">
<Label className="text-xs text-gray-600">Additional Notes</Label>
<Textarea placeholder="Any extra instructions for this case…" value={rx.additionalNotes} onChange={(e) => setRx((r) => ({ ...r, additionalNotes: e.target.value }))} className="text-sm resize-none" rows={3} />
</div>
</div>
{/* RX preview */}
<div className="border rounded-lg p-4 bg-gray-50 text-sm space-y-3">
<div className="text-center border-b pb-3">
<p className="font-bold text-base">Dental Lab Prescription</p>
<p className="text-xs text-gray-500">
Date: {today}{rx.dueDate ? ` · Due: ${new Date(rx.dueDate + "T00:00").toLocaleDateString("en-US")}` : ""}
</p>
</div>
<div className="grid grid-cols-2 gap-x-6 gap-y-1">
<div><span className="text-gray-400 text-xs">Patient</span><p className="font-medium">{patientName}</p></div>
<div><span className="text-gray-400 text-xs">Age</span><p>{rx.age || "—"}</p></div>
{rx.officeName && <div className="col-span-2"><span className="text-gray-400 text-xs">Dental Office</span><p className="font-medium">{rx.officeName}</p></div>}
<div className="col-span-2 mt-1 border-t pt-2"><span className="text-gray-400 text-xs">Lab</span><p className="font-medium">{rxTemplate.labName || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Phone</span><p>{rxTemplate.labPhone || "—"}</p></div>
{rxTemplate.labFax && <div><span className="text-gray-400 text-xs">Fax</span><p>{rxTemplate.labFax}</p></div>}
{rxTemplate.labAddress && <div className="col-span-2"><span className="text-gray-400 text-xs">Address</span><p>{rxTemplate.labAddress}</p></div>}
{rxTemplate.labAccount && <div><span className="text-gray-400 text-xs">Account #</span><p>{rxTemplate.labAccount}</p></div>}
<div className="col-span-2 mt-1 border-t pt-2 grid grid-cols-2 gap-x-6">
<div><span className="text-gray-400 text-xs">Case Type</span><p>{rxTemplate.caseType || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Material</span><p>{rxTemplate.material || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Tooth #</span><p>{rx.toothNumbers || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Shade</span><p>{rx.shade || "—"}</p></div>
{rx.doctorName && <div><span className="text-gray-400 text-xs">Doctor</span><p>{rx.doctorName}</p></div>}
</div>
{(rxTemplate.instructions || rx.additionalNotes) && (
<div className="col-span-2 mt-1 border-t pt-2">
<span className="text-gray-400 text-xs">Instructions</span>
<p className="whitespace-pre-wrap text-xs mt-1 text-gray-700">
{[rxTemplate.instructions, rx.additionalNotes].filter(Boolean).join("\n\n")}
</p>
</div>
)}
</div>
</div>
</div>
{/* Hidden print DOM */}
<div className="hidden">
<div ref={printRef}>
<h1>Dental Lab Prescription</h1>
<div className="subtitle">
Date: {today}{rx.dueDate ? ` · Due: ${new Date(rx.dueDate + "T00:00").toLocaleDateString("en-US")}` : ""}
</div>
<hr />
<div className="section">
<div className="section-title">Patient Information</div>
<div className="row">
<div className="field"><label>Patient Name</label><span>{patientName}</span></div>
<div className="field"><label>Age</label><span>{rx.age}</span></div>
<div className="field"><label>Dental Office</label><span>{rx.officeName}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Dental Lab</div>
<div className="row">
<div className="field"><label>Lab Name</label><span>{rxTemplate.labName ?? ""}</span></div>
<div className="field"><label>Account #</label><span>{rxTemplate.labAccount ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Phone</label><span>{rxTemplate.labPhone ?? ""}</span></div>
<div className="field"><label>Fax</label><span>{rxTemplate.labFax ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Address</label><span>{rxTemplate.labAddress ?? ""}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Prescription Details</div>
<div className="row">
<div className="field"><label>Case Type</label><span>{rxTemplate.caseType ?? ""}</span></div>
<div className="field"><label>Material</label><span>{rxTemplate.material ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Tooth Number(s)</label><span>{rx.toothNumbers}</span></div>
<div className="field"><label>Shade</label><span>{rx.shade}</span></div>
</div>
<div className="row">
<div className="field"><label>Doctor's Name</label><span>{rx.doctorName}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Instructions</div>
<div className="instructions-box">
{[rxTemplate.instructions, rx.additionalNotes].filter(Boolean).join("\n\n")}
</div>
</div>
<div className="sig-area">
<div className="sig-line">Doctor Signature</div>
<div className="sig-line">Date</div>
</div>
</div>
</div>
</>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setRxTemplate(null)}>Close</Button>
<Button onClick={handlePrint} className="gap-2">
<Printer className="h-4 w-4" /> Print Lab RX
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
@@ -0,0 +1,672 @@
import { useState, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import { Patient } from "@repo/db/types";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus, Pencil, Trash2, Printer, X, Check, FlaskConical, ChevronUp, ChevronDown } from "lucide-react";
type LabRxTemplate = {
id: number;
name: string;
labName?: string | null;
labPhone?: string | null;
labFax?: string | null;
labAddress?: string | null;
labAccount?: string | null;
caseType?: string | null;
material?: string | null;
instructions?: string | null;
sortOrder: number;
};
type TemplateFormState = {
name: string;
labName: string;
labPhone: string;
labFax: string;
labAddress: string;
labAccount: string;
caseType: string;
material: string;
instructions: string;
};
type RxFields = {
toothNumbers: string;
shade: string;
dueDate: string;
doctorName: string;
age: string;
officeName: string;
additionalNotes: string;
};
const EMPTY_TEMPLATE: TemplateFormState = {
name: "", labName: "", labPhone: "", labFax: "",
labAddress: "", labAccount: "", caseType: "", material: "", instructions: "Please make\nThank you!",
};
const EMPTY_RX: RxFields = { toothNumbers: "", shade: "", dueDate: "", doctorName: "", age: "", officeName: "", additionalNotes: "Please make\nThank you!" };
const calcAge = (dob: string | Date | null | undefined): string => {
if (!dob) return "";
const birth = new Date(dob);
const today = new Date();
let age = today.getFullYear() - birth.getFullYear();
const m = today.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
return String(age);
};
const CASE_TYPES = ["Crown", "Bridge", "Veneer", "Inlay/Onlay", "Full Denture", "Partial Denture", "Night Guard", "Retainer", "Implant Crown", "Other"];
const MATERIALS = ["Zirconia", "PFM (Porcelain-Fused-to-Metal)", "E-Max", "Acrylic", "Cast Metal", "Composite", "PMMA", "Other"];
interface Props {
open: boolean;
onClose: () => void;
patient: Patient;
}
export function LabRxModal({ open, onClose, patient }: Props) {
const { toast } = useToast();
const queryClient = useQueryClient();
const printRef = useRef<HTMLDivElement>(null);
// RX popup
const [rxTemplate, setRxTemplate] = useState<LabRxTemplate | null>(null);
const [rx, setRx] = useState<RxFields>(EMPTY_RX);
// Inline rename (Tab 1 cards)
const [renamingId, setRenamingId] = useState<number | null>(null);
const [renameValue, setRenameValue] = useState("");
// Manage Templates form
const [showTemplateForm, setShowTemplateForm] = useState(false);
const [editingTemplateId, setEditingTemplateId] = useState<number | null>(null);
const [templateForm, setTemplateForm] = useState<TemplateFormState>(EMPTY_TEMPLATE);
const { data: providers = [] } = useQuery<{ id: number; providerName: string }[]>({
queryKey: ["/api/npiProviders"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/npiProviders");
if (!res.ok) throw new Error("Failed to fetch providers");
return res.json();
},
enabled: open,
});
const { data: officeContact } = useQuery<{ officeName?: string } | null>({
queryKey: ["/api/office-contact"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/office-contact");
if (!res.ok) return null;
return res.json();
},
enabled: open,
});
const { data: templates = [], isLoading } = useQuery<LabRxTemplate[]>({
queryKey: ["/api/lab-rx/templates"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/lab-rx/templates");
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
},
enabled: open,
});
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["/api/lab-rx/templates"] });
const createMutation = useMutation({
mutationFn: async (data: TemplateFormState) => {
const res = await apiRequest("POST", "/api/lab-rx/templates", data);
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to save"); }
return res.json();
},
onSuccess: () => { invalidate(); setShowTemplateForm(false); setTemplateForm(EMPTY_TEMPLATE); toast({ title: "Template saved" }); },
onError: (e: any) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const updateMutation = useMutation({
mutationFn: async ({ id, data }: { id: number; data: Partial<TemplateFormState> }) => {
const res = await apiRequest("PUT", `/api/lab-rx/templates/${id}`, data);
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to update"); }
return res.json();
},
onSuccess: () => { invalidate(); setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); setShowTemplateForm(false); toast({ title: "Template updated" }); },
onError: (e: any) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const renameMutation = useMutation({
mutationFn: async ({ id, name }: { id: number; name: string }) => {
const res = await apiRequest("PUT", `/api/lab-rx/templates/${id}`, { name });
if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to rename"); }
return res.json();
},
onSuccess: () => { invalidate(); setRenamingId(null); },
onError: (e: any) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const deleteMutation = useMutation({
mutationFn: async (id: number) => {
const res = await apiRequest("DELETE", `/api/lab-rx/templates/${id}`);
if (!res.ok) throw new Error("Failed to delete");
},
onSuccess: () => { invalidate(); toast({ title: "Template deleted" }); },
onError: (e: any) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const reorderMutation = useMutation({
mutationFn: async (orderedIds: number[]) => {
const res = await apiRequest("POST", "/api/lab-rx/templates/reorder", { orderedIds });
if (!res.ok) throw new Error("Failed to reorder");
},
onSuccess: () => invalidate(),
onError: (e: any) => toast({ title: "Error", description: e.message, variant: "destructive" }),
});
const moveTemplate = (index: number, direction: "up" | "down") => {
const swapIndex = direction === "up" ? index - 1 : index + 1;
const reordered = [...templates];
const a = reordered[index]!;
const b = reordered[swapIndex]!;
reordered[index] = b;
reordered[swapIndex] = a;
queryClient.setQueryData<LabRxTemplate[]>(["/api/lab-rx/templates"], reordered);
reorderMutation.mutate(reordered.map((t) => t.id));
};
const openRx = (t: LabRxTemplate) => {
if (renamingId !== null) return;
setRxTemplate(t);
setRx({ ...EMPTY_RX, doctorName: providers[0]?.providerName ?? "", age: calcAge(patient.dateOfBirth), officeName: officeContact?.officeName ?? "" });
};
const startRename = (e: React.MouseEvent, t: LabRxTemplate) => {
e.stopPropagation();
setRenamingId(t.id);
setRenameValue(t.name);
};
const commitRename = () => {
if (!renameValue.trim()) { toast({ title: "Name cannot be empty", variant: "destructive" }); return; }
if (renamingId !== null) renameMutation.mutate({ id: renamingId, name: renameValue.trim() });
};
const cancelRename = () => setRenamingId(null);
const openAddTemplate = () => { setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); setShowTemplateForm(true); };
const openEditTemplate = (t: LabRxTemplate) => {
setEditingTemplateId(t.id);
setTemplateForm({ name: t.name, labName: t.labName ?? "", labPhone: t.labPhone ?? "", labFax: t.labFax ?? "", labAddress: t.labAddress ?? "", labAccount: t.labAccount ?? "", caseType: t.caseType ?? "", material: t.material ?? "", instructions: t.instructions ?? "" });
setShowTemplateForm(true);
};
const handleSaveTemplate = () => {
if (!templateForm.name.trim()) { toast({ title: "Template name is required", variant: "destructive" }); return; }
if (editingTemplateId !== null) updateMutation.mutate({ id: editingTemplateId, data: templateForm });
else createMutation.mutate(templateForm);
};
const isSaving = createMutation.isPending || updateMutation.isPending;
const handlePrint = () => {
const content = printRef.current;
if (!content || !rxTemplate) return;
const win = window.open("", "_blank", "width=800,height=900");
if (!win) return;
win.document.write(`<!DOCTYPE html><html><head><title>Lab RX</title>
<style>
body{font-family:Arial,sans-serif;margin:0;padding:24px;font-size:13px;color:#111}
h1{font-size:22px;margin:0 0 4px;text-align:center}
.subtitle{text-align:center;color:#555;font-size:12px;margin-bottom:12px}
hr{border:none;border-top:2px solid #000;margin:10px 0}
.section{margin-bottom:14px}
.section-title{font-weight:bold;font-size:11px;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid #bbb;margin-bottom:8px;padding-bottom:3px;color:#333}
.row{display:flex;gap:24px;margin-bottom:6px}
.field{flex:1}
.field label{display:block;font-size:10px;color:#777;margin-bottom:1px}
.field span{display:block;border-bottom:1px solid #aaa;padding-bottom:2px;min-height:18px;font-size:13px}
.instructions-box{border:1px solid #bbb;padding:8px;min-height:64px;white-space:pre-wrap;font-size:12px}
.sig-area{display:flex;gap:48px;margin-top:32px}
.sig-line{border-top:1px solid #000;padding-top:3px;font-size:10px;color:#777;min-width:180px}
@media print{body{margin:0}}
</style>
</head><body>${content.innerHTML}</body></html>`);
win.document.close();
win.focus();
setTimeout(() => { win.print(); win.close(); }, 300);
};
const patientName = `${patient.firstName} ${patient.lastName}`;
const today = new Date().toLocaleDateString("en-US");
return (
<>
{/* ── Main modal ── */}
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FlaskConical className="h-5 w-5" />
Lab RX — {patientName}
</DialogTitle>
</DialogHeader>
<Tabs defaultValue="rx">
<TabsList className="mb-4">
<TabsTrigger value="rx">Create Lab RX</TabsTrigger>
<TabsTrigger value="templates">Manage Templates</TabsTrigger>
</TabsList>
{/* ── Tab 1: pick template ── */}
<TabsContent value="rx" className="space-y-4">
<p className="text-xs font-medium text-gray-500 uppercase tracking-wide">
Click a template to create a Lab RX
</p>
{isLoading ? (
<p className="text-sm text-gray-400">Loading templates…</p>
) : templates.length === 0 ? (
<p className="text-sm text-gray-400">No templates yet. Go to "Manage Templates" to add one.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{templates.map((t) => (
<div
key={t.id}
onClick={() => openRx(t)}
className="group relative border rounded-lg p-3 bg-white cursor-pointer hover:border-blue-400 hover:shadow-sm transition-all"
>
{renamingId === t.id ? (
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<Input
autoFocus
value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") commitRename(); if (e.key === "Escape") cancelRename(); }}
className="h-7 text-sm font-medium"
/>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 text-green-600" onClick={commitRename} disabled={renameMutation.isPending}>
<Check className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0" onClick={cancelRename}>
<X className="h-3.5 w-3.5" />
</Button>
</div>
) : (
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="font-semibold text-sm text-gray-900 truncate">{t.name}</p>
{t.labName && <p className="text-xs text-gray-500 mt-0.5">{t.labName}</p>}
<div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-1 text-xs text-gray-400">
{t.caseType && <span>{t.caseType}</span>}
{t.material && <span>{t.material}</span>}
</div>
</div>
<button
title="Rename template"
onClick={(e) => startRename(e, t)}
className="shrink-0 p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-gray-100 transition-opacity"
>
<Pencil className="h-3.5 w-3.5 text-gray-400" />
</button>
</div>
)}
</div>
))}
</div>
)}
</TabsContent>
{/* ── Tab 2: manage templates ── */}
<TabsContent value="templates" className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">{templates.length} template{templates.length !== 1 ? "s" : ""}</p>
<Button size="sm" onClick={openAddTemplate} className="gap-1.5">
<Plus className="h-4 w-4" /> Add Template
</Button>
</div>
{showTemplateForm && (
<div className="border rounded-lg overflow-hidden">
<div className="bg-gray-100 border-b px-4 py-2.5">
<p className="text-sm font-semibold text-gray-800">
{editingTemplateId !== null ? "Edit Template" : "New Template"}
</p>
</div>
<div className="p-4 space-y-5 bg-white">
{/* Template Name section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-blue-600">Template Name</p>
<div className="flex-1 h-px bg-blue-100" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Name <span className="text-red-500">*</span></Label>
<Input
placeholder="e.g. Glidewell Crown, Argen Bridge…"
value={templateForm.name}
onChange={(e) => setTemplateForm((f) => ({ ...f, name: e.target.value }))}
className="h-9 text-sm"
/>
<p className="text-xs text-gray-400">This name appears on the template picker button.</p>
</div>
</div>
{/* Lab Information section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Lab Information</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Lab Name</Label>
<Input placeholder="e.g. Glidewell Dental" value={templateForm.labName} onChange={(e) => setTemplateForm((f) => ({ ...f, labName: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Account #</Label>
<Input placeholder="e.g. 123456" value={templateForm.labAccount} onChange={(e) => setTemplateForm((f) => ({ ...f, labAccount: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Phone</Label>
<Input placeholder="(800) 000-0000" value={templateForm.labPhone} onChange={(e) => setTemplateForm((f) => ({ ...f, labPhone: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Fax</Label>
<Input placeholder="(800) 000-0001" value={templateForm.labFax} onChange={(e) => setTemplateForm((f) => ({ ...f, labFax: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Address</Label>
<Input placeholder="123 Lab St, City, State 00000" value={templateForm.labAddress} onChange={(e) => setTemplateForm((f) => ({ ...f, labAddress: e.target.value }))} className="h-9 text-sm" />
</div>
</div>
</div>
{/* Default Case Details section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Default Case Details</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Case Type</Label>
<Select value={templateForm.caseType} onValueChange={(v) => setTemplateForm((f) => ({ ...f, caseType: v }))}>
<SelectTrigger className="h-9 text-sm"><SelectValue placeholder="Select…" /></SelectTrigger>
<SelectContent>{CASE_TYPES.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Material</Label>
<Select value={templateForm.material} onValueChange={(v) => setTemplateForm((f) => ({ ...f, material: v }))}>
<SelectTrigger className="h-9 text-sm"><SelectValue placeholder="Select…" /></SelectTrigger>
<SelectContent>{MATERIALS.map((m) => <SelectItem key={m} value={m}>{m}</SelectItem>)}</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Instructions</Label>
<Textarea placeholder="Standard instructions for this lab…" value={templateForm.instructions} onChange={(e) => setTemplateForm((f) => ({ ...f, instructions: e.target.value }))} className="text-sm resize-none" rows={3} />
</div>
</div>
</div>
</div>
<div className="bg-gray-50 border-t px-4 py-3 flex gap-2">
<Button size="sm" onClick={handleSaveTemplate} disabled={isSaving}>
<Check className="h-3.5 w-3.5 mr-1" />{isSaving ? "Saving…" : "Save Template"}
</Button>
<Button size="sm" variant="outline" onClick={() => { setShowTemplateForm(false); setEditingTemplateId(null); setTemplateForm(EMPTY_TEMPLATE); }} disabled={isSaving}>
<X className="h-3.5 w-3.5 mr-1" /> Cancel
</Button>
</div>
</div>
)}
{isLoading ? (
<p className="text-sm text-gray-400 py-4 text-center">Loading…</p>
) : templates.length === 0 ? (
<p className="text-sm text-gray-400 py-8 text-center">No templates yet. Click "Add Template" to create one.</p>
) : (
<div className="space-y-2">
{templates.map((t, index) => (
<div key={t.id} className="border rounded-lg p-3 bg-white flex items-center gap-2">
<div className="flex flex-col shrink-0">
<Button
variant="ghost" size="icon" className="h-6 w-6 text-gray-400 hover:text-gray-700 disabled:opacity-20"
disabled={index === 0 || reorderMutation.isPending}
onClick={() => moveTemplate(index, "up")}
>
<ChevronUp className="h-4 w-4" />
</Button>
<Button
variant="ghost" size="icon" className="h-6 w-6 text-gray-400 hover:text-gray-700 disabled:opacity-20"
disabled={index === templates.length - 1 || reorderMutation.isPending}
onClick={() => moveTemplate(index, "down")}
>
<ChevronDown className="h-4 w-4" />
</Button>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-sm text-gray-900">{t.name}</p>
{t.labName && <p className="text-xs text-gray-500">{t.labName}</p>}
<div className="flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-gray-400 mt-0.5">
{t.caseType && <span>Case: {t.caseType}</span>}
{t.material && <span>Material: {t.material}</span>}
{t.labPhone && <span>Ph: {t.labPhone}</span>}
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => openEditTemplate(t)}>
<Pencil className="h-3.5 w-3.5 text-gray-500" />
</Button>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => deleteMutation.mutate(t.id)} disabled={deleteMutation.isPending}>
<Trash2 className="h-3.5 w-3.5 text-red-500" />
</Button>
</div>
</div>
))}
</div>
)}
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
{/* ── RX popup ── */}
<Dialog open={!!rxTemplate} onOpenChange={(v) => { if (!v) setRxTemplate(null); }}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Printer className="h-5 w-5" />
Lab RX — {rxTemplate?.name}
</DialogTitle>
</DialogHeader>
{rxTemplate && (
<>
<div className="space-y-5 py-2">
{/* Tooth Number section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-blue-600">Tooth Number</p>
<div className="flex-1 h-px bg-blue-100" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs text-gray-600">Tooth Number(s)</Label>
<Input placeholder="e.g. 3, 14, 19" value={rx.toothNumbers} onChange={(e) => setRx((r) => ({ ...r, toothNumbers: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Shade</Label>
<Input placeholder="e.g. A2, B1" value={rx.shade} onChange={(e) => setRx((r) => ({ ...r, shade: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Age</Label>
<Input placeholder="e.g. 72" value={rx.age} onChange={(e) => setRx((r) => ({ ...r, age: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Due Date</Label>
<Input type="date" value={rx.dueDate} onChange={(e) => setRx((r) => ({ ...r, dueDate: e.target.value }))} className="h-9 text-sm" />
</div>
<div className="space-y-1">
<Label className="text-xs text-gray-600">Doctor's Name</Label>
<Select value={rx.doctorName} onValueChange={(v) => setRx((r) => ({ ...r, doctorName: v }))}>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="Select provider…" />
</SelectTrigger>
<SelectContent>
{providers.map((p) => (
<SelectItem key={p.id} value={p.providerName}>{p.providerName}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="col-span-2 space-y-1">
<Label className="text-xs text-gray-600">Dental Office Name</Label>
<Input placeholder="e.g. Summit Dental Care" value={rx.officeName} onChange={(e) => setRx((r) => ({ ...r, officeName: e.target.value }))} className="h-9 text-sm" />
</div>
</div>
</div>
{/* Instructions section */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Instructions</p>
<div className="flex-1 h-px bg-gray-200" />
</div>
{rxTemplate.instructions && (
<div className="rounded-md bg-gray-50 border px-3 py-2 text-xs text-gray-600 whitespace-pre-wrap">
<span className="font-medium text-gray-400 block mb-1">From template</span>
{rxTemplate.instructions}
</div>
)}
<div className="space-y-1">
<Label className="text-xs text-gray-600">Additional Notes</Label>
<Textarea placeholder="Any extra instructions for this case…" value={rx.additionalNotes} onChange={(e) => setRx((r) => ({ ...r, additionalNotes: e.target.value }))} className="text-sm resize-none" rows={3} />
</div>
</div>
{/* RX preview */}
<div className="border rounded-lg p-4 bg-gray-50 text-sm space-y-3">
<div className="text-center border-b pb-3">
<p className="font-bold text-base">Dental Lab Prescription</p>
<p className="text-xs text-gray-500">
Date: {today}{rx.dueDate ? ` · Due: ${new Date(rx.dueDate + "T00:00").toLocaleDateString("en-US")}` : ""}
</p>
</div>
<div className="grid grid-cols-2 gap-x-6 gap-y-1">
<div><span className="text-gray-400 text-xs">Patient</span><p className="font-medium">{patientName}</p></div>
<div><span className="text-gray-400 text-xs">Age</span><p>{rx.age || "—"}</p></div>
{rx.officeName && <div className="col-span-2"><span className="text-gray-400 text-xs">Dental Office</span><p className="font-medium">{rx.officeName}</p></div>}
<div className="col-span-2 mt-1 border-t pt-2"><span className="text-gray-400 text-xs">Lab</span><p className="font-medium">{rxTemplate.labName || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Phone</span><p>{rxTemplate.labPhone || "—"}</p></div>
{rxTemplate.labFax && <div><span className="text-gray-400 text-xs">Fax</span><p>{rxTemplate.labFax}</p></div>}
{rxTemplate.labAddress && <div className="col-span-2"><span className="text-gray-400 text-xs">Address</span><p>{rxTemplate.labAddress}</p></div>}
{rxTemplate.labAccount && <div><span className="text-gray-400 text-xs">Account #</span><p>{rxTemplate.labAccount}</p></div>}
<div className="col-span-2 mt-1 border-t pt-2 grid grid-cols-2 gap-x-6">
<div><span className="text-gray-400 text-xs">Case Type</span><p>{rxTemplate.caseType || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Material</span><p>{rxTemplate.material || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Tooth #</span><p>{rx.toothNumbers || "—"}</p></div>
<div><span className="text-gray-400 text-xs">Shade</span><p>{rx.shade || "—"}</p></div>
{rx.doctorName && <div><span className="text-gray-400 text-xs">Doctor</span><p>{rx.doctorName}</p></div>}
</div>
{(rxTemplate.instructions || rx.additionalNotes) && (
<div className="col-span-2 mt-1 border-t pt-2">
<span className="text-gray-400 text-xs">Instructions</span>
<p className="whitespace-pre-wrap text-xs mt-1 text-gray-700">
{[rxTemplate.instructions, rx.additionalNotes].filter(Boolean).join("\n\n")}
</p>
</div>
)}
</div>
</div>
</div>
{/* Hidden print DOM */}
<div className="hidden">
<div ref={printRef}>
<h1>Dental Lab Prescription</h1>
<div className="subtitle">
Date: {today}{rx.dueDate ? ` · Due: ${new Date(rx.dueDate + "T00:00").toLocaleDateString("en-US")}` : ""}
</div>
<hr />
<div className="section">
<div className="section-title">Patient Information</div>
<div className="row">
<div className="field"><label>Patient Name</label><span>{patientName}</span></div>
<div className="field"><label>Age</label><span>{rx.age}</span></div>
<div className="field"><label>Dental Office</label><span>{rx.officeName}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Dental Lab</div>
<div className="row">
<div className="field"><label>Lab Name</label><span>{rxTemplate.labName ?? ""}</span></div>
<div className="field"><label>Account #</label><span>{rxTemplate.labAccount ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Phone</label><span>{rxTemplate.labPhone ?? ""}</span></div>
<div className="field"><label>Fax</label><span>{rxTemplate.labFax ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Address</label><span>{rxTemplate.labAddress ?? ""}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Prescription Details</div>
<div className="row">
<div className="field"><label>Case Type</label><span>{rxTemplate.caseType ?? ""}</span></div>
<div className="field"><label>Material</label><span>{rxTemplate.material ?? ""}</span></div>
</div>
<div className="row">
<div className="field"><label>Tooth Number(s)</label><span>{rx.toothNumbers}</span></div>
<div className="field"><label>Shade</label><span>{rx.shade}</span></div>
</div>
<div className="row">
<div className="field"><label>Doctor's Name</label><span>{rx.doctorName}</span></div>
</div>
</div>
<div className="section">
<div className="section-title">Instructions</div>
<div className="instructions-box">
{[rxTemplate.instructions, rx.additionalNotes].filter(Boolean).join("\n\n")}
</div>
</div>
<div className="sig-area">
<div className="sig-line">Doctor Signature</div>
<div className="sig-line">Date</div>
</div>
</div>
</div>
</>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setRxTemplate(null)}>Close</Button>
<Button onClick={handlePrint} className="gap-2">
<Printer className="h-4 w-4" /> Print Lab RX
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
@@ -419,7 +419,7 @@ export function DdmaEligibilityButton({
) : ( ) : (
<> <>
<CheckCircle className="h-4 w-4 mr-2" /> <CheckCircle className="h-4 w-4 mr-2" />
Delta MA Claim MD
</> </>
)} )}
</Button> </Button>
+3 -194
View File
@@ -2,7 +2,6 @@ import { useState, useRef, useEffect } from "react";
import { import {
Bot, Bot,
X, X,
ChevronRight,
Stethoscope, Stethoscope,
Calendar, Calendar,
FileText, FileText,
@@ -13,7 +12,6 @@ import {
Paperclip, Paperclip,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { apiRequest } from "@/lib/queryClient"; import { apiRequest } from "@/lib/queryClient";
@@ -21,8 +19,6 @@ import { setChatbotPendingFiles } from "@/lib/chatbotFileStore";
type Step = type Step =
| "menu" | "menu"
| "eligibility-input"
| "eligibility-confirm"
| "ai-loading" | "ai-loading"
| "patient-found" | "patient-found"
| "eligibility-id-ready" | "eligibility-id-ready"
@@ -53,12 +49,6 @@ interface PatientResult {
dateOfBirth: string | null; dateOfBirth: string | null;
} }
interface EligibilityData {
memberId: string;
dob: string;
dobISO: string;
}
interface CheckAndClaimData { interface CheckAndClaimData {
patient: PatientResult | null; patient: PatientResult | null;
memberId: string; memberId: string;
@@ -75,40 +65,6 @@ function makeMsg(role: "bot" | "user", text: string, isLoading = false): Message
return { id: ++msgCounter, role, text, isLoading }; return { id: ++msgCounter, role, text, isLoading };
} }
function getAutoCheck(dobISO: string): "mh" | "cmsp" {
const [y, m, d] = dobISO.split("-").map(Number);
const today = new Date();
let age = today.getFullYear() - (y ?? 0);
const monthDiff = today.getMonth() + 1 - (m ?? 0);
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < (d ?? 0))) age--;
return age >= 21 ? "mh" : "cmsp";
}
function parseEligibilityInput(
raw: string
): { memberId: string; display: string; iso: string } | null {
const dateMatch = raw.match(/\b(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})\b/);
if (!dateMatch) return null;
const m = dateMatch[1];
const d = dateMatch[2];
const y = dateMatch[3];
if (!m || !d || !y) return null;
const month = parseInt(m, 10);
const day = parseInt(d, 10);
const year = parseInt(y, 10);
if (month < 1 || month > 12 || day < 1 || day > 31 || year < 1900) return null;
const withoutDate = raw.replace(dateMatch[0], "");
const memberId = (withoutDate.match(/[a-zA-Z0-9]/g) ?? []).join("");
if (!memberId) return null;
return {
memberId,
display: `${m.padStart(2, "0")}/${d.padStart(2, "0")}/${y}`,
iso: `${y}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`,
};
}
const CHAT_STORAGE_KEY = "chatbot_messages"; const CHAT_STORAGE_KEY = "chatbot_messages";
const CHATBOT_JOB_TS_KEY = "chatbot_job_started_at"; const CHATBOT_JOB_TS_KEY = "chatbot_job_started_at";
@@ -158,9 +114,6 @@ export function ChatbotButton() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [step, setStep] = useState<Step>("menu"); const [step, setStep] = useState<Step>("menu");
const [messages, setMessages] = useState<Message[]>(loadSavedMessages); const [messages, setMessages] = useState<Message[]>(loadSavedMessages);
const [pasteInput, setPasteInput] = useState("");
const [parseError, setParseError] = useState("");
const [eligibilityData, setEligibilityData] = useState<EligibilityData | null>(null);
const [freeTextInput, setFreeTextInput] = useState(""); const [freeTextInput, setFreeTextInput] = useState("");
const [patientResult, setPatientResult] = useState<PatientResult | null>(null); const [patientResult, setPatientResult] = useState<PatientResult | null>(null);
const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null); const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null);
@@ -206,7 +159,6 @@ export function ChatbotButton() {
const [pendingFiles, setPendingFiles] = useState<File[]>([]); const [pendingFiles, setPendingFiles] = useState<File[]>([]);
const [, setLocation] = useLocation(); const [, setLocation] = useLocation();
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const pasteRef = useRef<HTMLTextAreaElement>(null);
const freeTextRef = useRef<HTMLTextAreaElement>(null); const freeTextRef = useRef<HTMLTextAreaElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@@ -244,9 +196,6 @@ export function ChatbotButton() {
}, [messages]); }, [messages]);
useEffect(() => { useEffect(() => {
if (step === "eligibility-input") {
setTimeout(() => pasteRef.current?.focus(), 50);
}
if (step === "menu") { if (step === "menu") {
setTimeout(() => freeTextRef.current?.focus(), 50); setTimeout(() => freeTextRef.current?.focus(), 50);
} }
@@ -266,9 +215,6 @@ export function ChatbotButton() {
// Resets step/data only — keeps message history // Resets step/data only — keeps message history
const resetStep = () => { const resetStep = () => {
setStep("menu"); setStep("menu");
setPasteInput("");
setParseError("");
setEligibilityData(null);
setFreeTextInput(""); setFreeTextInput("");
setPatientResult(null); setPatientResult(null);
setEligibilityIdData(null); setEligibilityIdData(null);
@@ -300,7 +246,7 @@ export function ChatbotButton() {
resetStep(); resetStep();
}; };
const handleOptionSelect = (option: "eligibility" | "schedule" | "claims") => { const handleOptionSelect = (option: "schedule" | "claims") => {
if (option === "schedule") { if (option === "schedule") {
addMsg("user", "Schedule an appointment"); addMsg("user", "Schedule an appointment");
addMsg("bot", "Opening the appointments page..."); addMsg("bot", "Opening the appointments page...");
@@ -309,67 +255,9 @@ export function ChatbotButton() {
addMsg("user", "View claims"); addMsg("user", "View claims");
addMsg("bot", "Opening the claims page..."); addMsg("bot", "Opening the claims page...");
setTimeout(() => { setLocation("/claims"); setOpen(false); resetStep(); }, 600); setTimeout(() => { setLocation("/claims"); setOpen(false); resetStep(); }, 600);
} else if (option === "eligibility") {
addMsg("user", "Check Eligibility");
addMsg("bot", "Please enter the patient's Member ID and Date of Birth:");
setStep("eligibility-input");
} }
}; };
const handleEligibilitySubmit = () => {
const parsed = parseEligibilityInput(pasteInput);
if (!parsed) {
setParseError("Couldn't find both a Member ID and a date. Try: 123456789 01/15/1985");
return;
}
setParseError("");
const data: EligibilityData = { memberId: parsed.memberId, dob: parsed.display, dobISO: parsed.iso };
setEligibilityData(data);
addMsg("user", pasteInput.trim());
addMsg("bot", `Ready to check MassHealth eligibility for:\n• Member ID: ${parsed.memberId}\n• Date of Birth: ${parsed.display}\n\nShall I proceed?`);
setStep("eligibility-confirm");
};
const handleConfirm = () => {
if (!eligibilityData) return;
addMsg("user", "Yes, check now");
addMsg("bot", "Opening the eligibility check page with this patient...");
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({
memberId: eligibilityData.memberId,
dob: eligibilityData.dobISO,
autoCheck: getAutoCheck(eligibilityData.dobISO),
}));
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
setTimeout(() => { setLocation("/insurance-status"); setOpen(false); resetStep(); }, 600);
};
const handleEligibilityFromPatient = () => {
if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return;
addMsg("user", "Check eligibility now");
addMsg("bot", "Opening the eligibility check page...");
prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth));
};
const handleEligibilityAndAppointmentFromPatient = async () => {
if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return;
addMsg("user", "Check eligibility & add to schedule (today)");
addMsg("bot", "Creating appointment for today...", true);
try {
const res = await apiRequest("POST", "/api/ai/create-appointment-today", {
patientId: patientResult.id,
});
const data = await res.json();
if (!res.ok) {
replaceLastMsg(data.message ?? "Could not create appointment.");
return;
}
replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) — opening eligibility check page...`);
} catch {
replaceLastMsg("Could not create appointment — opening eligibility check page...");
}
prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth));
};
const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string) => { const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string) => {
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck })); sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck }));
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill")); window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
@@ -728,14 +616,7 @@ export function ChatbotButton() {
{/* Quick-action buttons (menu step) */} {/* Quick-action buttons (menu step) */}
{step === "menu" && ( {step === "menu" && (
<div className="grid grid-cols-2 gap-2 pt-1"> <div className="grid grid-cols-1 gap-2 pt-1">
<button
onClick={() => handleOptionSelect("eligibility")}
className="flex items-center gap-2 p-3 rounded-xl border border-primary/30 hover:bg-primary/5 text-sm text-left transition-colors"
>
<Stethoscope className="h-4 w-4 text-primary shrink-0" />
<span>Check Eligibility</span>
</button>
<button <button
onClick={() => handleOptionSelect("schedule")} onClick={() => handleOptionSelect("schedule")}
className="flex items-center gap-2 p-3 rounded-xl border border-blue-300 hover:bg-blue-50 text-sm text-left transition-colors" className="flex items-center gap-2 p-3 rounded-xl border border-blue-300 hover:bg-blue-50 text-sm text-left transition-colors"
@@ -745,7 +626,7 @@ export function ChatbotButton() {
</button> </button>
<button <button
onClick={() => handleOptionSelect("claims")} onClick={() => handleOptionSelect("claims")}
className="flex items-center gap-2 p-3 rounded-xl border border-orange-300 hover:bg-orange-50 text-sm text-left transition-colors col-span-2" className="flex items-center gap-2 p-3 rounded-xl border border-orange-300 hover:bg-orange-50 text-sm text-left transition-colors"
> >
<FileText className="h-4 w-4 text-orange-500 shrink-0" /> <FileText className="h-4 w-4 text-orange-500 shrink-0" />
<span>Claims</span> <span>Claims</span>
@@ -753,58 +634,6 @@ export function ChatbotButton() {
</div> </div>
)} )}
{/* Eligibility manual input */}
{step === "eligibility-input" && (
<div className="space-y-2 bg-gray-50 rounded-xl p-3 border">
<Label className="text-xs text-gray-600">Paste Member ID and Date of Birth</Label>
<textarea
ref={pasteRef}
rows={3}
placeholder={"e.g. 123456789 01/15/1985\nor 01/15/1985 123456789"}
value={pasteInput}
onChange={(e) => { setPasteInput(e.target.value); setParseError(""); }}
className={cn(
"w-full rounded-md border bg-white px-3 py-2 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring",
parseError && "border-red-400"
)}
/>
{parseError && <p className="text-xs text-red-500">{parseError}</p>}
<div className="flex gap-2">
<Button
size="sm"
className="flex-1 h-8 text-xs"
onClick={handleEligibilitySubmit}
disabled={!pasteInput.trim()}
>
Continue <ChevronRight className="h-3 w-3 ml-1" />
</Button>
<Button size="sm" variant="ghost" className="h-8 text-xs" onClick={resetStep}>
Cancel
</Button>
</div>
</div>
)}
{/* Eligibility confirm */}
{step === "eligibility-confirm" && (
<div className="flex gap-2">
<Button size="sm" className="flex-1 h-8 text-xs" onClick={handleConfirm}>
Yes, Check Now
</Button>
<Button
size="sm"
variant="outline"
className="h-8 text-xs"
onClick={() => {
setStep("eligibility-input");
setMessages((prev) => prev.slice(0, -2));
}}
>
Edit
</Button>
</div>
)}
{/* AI-found patient card */} {/* AI-found patient card */}
{step === "patient-found" && patientResult && ( {step === "patient-found" && patientResult && (
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3 space-y-2"> <div className="bg-blue-50 border border-blue-200 rounded-xl p-3 space-y-2">
@@ -821,26 +650,6 @@ export function ChatbotButton() {
<p className="text-xs text-gray-500">DOB: {patientResult.dateOfBirth}</p> <p className="text-xs text-gray-500">DOB: {patientResult.dateOfBirth}</p>
)} )}
<div className="flex flex-col gap-2 pt-1"> <div className="flex flex-col gap-2 pt-1">
{patientResult.insuranceId && patientResult.dateOfBirth && (
<>
<Button
size="sm"
className="w-full h-8 text-xs bg-primary hover:bg-primary/90"
onClick={handleEligibilityFromPatient}
>
<Stethoscope className="h-3 w-3 mr-1" />
Check Eligibility
</Button>
<Button
size="sm"
className="w-full h-8 text-xs bg-emerald-600 hover:bg-emerald-700 text-white"
onClick={handleEligibilityAndAppointmentFromPatient}
>
<Calendar className="h-3 w-3 mr-1" />
Eligibility &amp; Appointment Today
</Button>
</>
)}
<Button size="sm" variant="ghost" className="w-full h-8 text-xs" onClick={resetStep}> <Button size="sm" variant="ghost" className="w-full h-8 text-xs" onClick={resetStep}>
Cancel Cancel
</Button> </Button>
@@ -30,6 +30,8 @@ import {
Building2, Building2,
Timer, Timer,
BookOpen, BookOpen,
MonitorCog,
GraduationCap,
ShoppingCart, ShoppingCart,
Search, Search,
KeyRound, KeyRound,
@@ -169,6 +171,11 @@ export function Sidebar() {
path: "/cloud-storage", path: "/cloud-storage",
icon: <Cloud className="h-5 w-5 text-sky-500" />, icon: <Cloud className="h-5 w-5 text-sky-500" />,
}, },
{
name: "Dental Education",
path: "/dental-education",
icon: <GraduationCap className="h-5 w-5 text-lime-600" />,
},
{ {
name: "AI Input Agent", name: "AI Input Agent",
path: "/ai-input-agent", path: "/ai-input-agent",
@@ -285,6 +292,11 @@ export function Sidebar() {
path: "/settings/aichat", path: "/settings/aichat",
icon: <Bot className="h-4 w-4 text-gray-400" />, icon: <Bot className="h-4 w-4 text-gray-400" />,
}, },
{
name: "Selenium Settings",
path: "/settings/selenium",
icon: <MonitorCog className="h-4 w-4 text-gray-400" />,
},
], ],
}, },
], ],
@@ -46,7 +46,7 @@ export function TopAppBar() {
</svg> </svg>
<h1 className="text-lg font-medium text-primary"> <h1 className="text-lg font-medium text-primary">
My Dental Office Management Wellspring Acupuncture Office Management
</h1> </h1>
</div> </div>
</Link> </Link>
@@ -0,0 +1,106 @@
import { useState, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { CheckCircle } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
export function SeleniumSettingsCard() {
const { toast } = useToast();
const [paymentGroupId, setPaymentGroupId] = useState("");
const { data: settings, isLoading } = useQuery({
queryKey: ["/api/selenium-settings"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/selenium-settings");
if (!res.ok) return null;
return res.json();
},
});
useEffect(() => {
if (settings) {
setPaymentGroupId(settings.paymentGroupId ?? "");
}
}, [settings]);
const saveMutation = useMutation({
mutationFn: async (data) => {
const res = await apiRequest("PUT", "/api/selenium-settings", data);
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.message || "Failed to save Selenium settings");
}
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/selenium-settings"] });
toast({ title: "Selenium Settings Saved" });
},
onError: (err) => {
toast({ title: "Error", description: err?.message || "Failed to save settings", variant: "destructive" });
},
});
if (isLoading) {
return (
<Card>
<CardContent className="py-6">
<p className="text-sm text-gray-500">Loading...</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardContent className="space-y-6 py-6">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold">Selenium Settings</h3>
{settings?.paymentGroupId && (
<span className="flex items-center gap-1 text-xs text-green-600 font-medium">
<CheckCircle className="h-3.5 w-3.5" /> Configured
</span>
)}
</div>
<p className="text-sm text-gray-500 mt-1">
Configure values used by Selenium automation workers (United eligibility, claim, and pre-auth).
</p>
</div>
<form
className="space-y-4"
onSubmit={(e) => {
e.preventDefault();
saveMutation.mutate({ paymentGroupId });
}}
>
<div>
<label className="block text-sm font-medium text-gray-700">
Payment Group ID
</label>
<p className="text-xs text-gray-400 mb-1">
The office name as it appears in the United/DentalHub portal dropdown (e.g. "Summit Dental Care" or "Broadway Dental").
</p>
<input
type="text"
value={paymentGroupId}
onChange={(e) => setPaymentGroupId(e.target.value)}
className="mt-1 p-2 border rounded w-full"
placeholder="e.g. Summit Dental Care"
/>
</div>
<button
type="submit"
className="bg-teal-600 text-white px-4 py-2 rounded hover:bg-teal-700 disabled:opacity-50"
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? "Saving..." : "Save Settings"}
</button>
</form>
</CardContent>
</Card>
);
}
@@ -0,0 +1,110 @@
import { useState, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { CheckCircle } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
type SeleniumSettings = {
paymentGroupId: string;
};
export function SeleniumSettingsCard() {
const { toast } = useToast();
const [paymentGroupId, setPaymentGroupId] = useState("");
const { data: settings, isLoading } = useQuery<SeleniumSettings | null>({
queryKey: ["/api/selenium-settings"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/selenium-settings");
if (!res.ok) return null;
return res.json();
},
});
useEffect(() => {
if (settings) {
setPaymentGroupId(settings.paymentGroupId ?? "");
}
}, [settings]);
const saveMutation = useMutation({
mutationFn: async (data: SeleniumSettings) => {
const res = await apiRequest("PUT", "/api/selenium-settings", data);
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.message || "Failed to save Selenium settings");
}
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/selenium-settings"] });
toast({ title: "Selenium Settings Saved" });
},
onError: (err: any) => {
toast({ title: "Error", description: err?.message || "Failed to save settings", variant: "destructive" });
},
});
if (isLoading) {
return (
<Card>
<CardContent className="py-6">
<p className="text-sm text-gray-500">Loading...</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardContent className="space-y-6 py-6">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold">Selenium Settings</h3>
{settings?.paymentGroupId && (
<span className="flex items-center gap-1 text-xs text-green-600 font-medium">
<CheckCircle className="h-3.5 w-3.5" /> Configured
</span>
)}
</div>
<p className="text-sm text-gray-500 mt-1">
Configure values used by Selenium automation workers (United eligibility, claim, and pre-auth).
</p>
</div>
<form
className="space-y-4"
onSubmit={(e) => {
e.preventDefault();
saveMutation.mutate({ paymentGroupId });
}}
>
<div>
<label className="block text-sm font-medium text-gray-700">
Payment Group ID
</label>
<p className="text-xs text-gray-400 mb-1">
The office name as it appears in the United/DentalHub portal dropdown (e.g. "Summit Dental Care" or "Broadway Dental").
</p>
<input
type="text"
value={paymentGroupId}
onChange={(e) => setPaymentGroupId(e.target.value)}
className="mt-1 p-2 border rounded w-full"
placeholder="e.g. Summit Dental Care"
/>
</div>
<button
type="submit"
className="bg-teal-600 text-white px-4 py-2 rounded hover:bg-teal-700 disabled:opacity-50"
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? "Saving..." : "Save Settings"}
</button>
</form>
</CardContent>
</Card>
);
}
@@ -40,6 +40,27 @@ export function DateInput({
} }
}, [value]); }, [value]);
const handleBlur = () => {
// If the user finished typing a 2-digit year (e.g. "02/12/03") the live-typing
// logic below never commits it, since it waits for a 4-digit year. On blur,
// expand a trailing 2-digit year so the date isn't silently dropped.
const match = inputValue.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2})$/);
if (!match) return;
const [, mmStr, ddStr, yyStr] = match;
const mm = mmStr.padStart(2, "0");
const dd = ddStr.padStart(2, "0");
const currentYearLastTwo = new Date().getFullYear() % 100;
const yy = parseInt(yyStr, 10);
const yyyy = yy <= currentYearLastTwo ? 2000 + yy : 1900 + yy;
const parsed = new Date(yyyy, +mm - 1, +dd);
if (!isNaN(parsed.getTime())) {
setInputValue(format(parsed, "MM/dd/yyyy"));
onChange(parsed);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const raw = e.target.value; const raw = e.target.value;
@@ -148,6 +169,7 @@ export function DateInput({
placeholder="MM/DD/YYYY" placeholder="MM/DD/YYYY"
value={inputValue} value={inputValue}
onChange={handleInputChange} onChange={handleInputChange}
onBlur={handleBlur}
/> />
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@@ -0,0 +1,40 @@
export type LabRxTemplateConfig = {
id: string;
name: string;
labName?: string;
labPhone?: string;
labFax?: string;
labAddress?: string;
labAccount?: string;
caseType?: string;
material?: string;
instructions?: string;
};
export const LAB_RX_TEMPLATES: LabRxTemplateConfig[] = [
{
id: "highland-crown",
name: "Highland Dental Crown",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Crown",
material: "Zirconia",
},
{
id: "highland-full-denture",
name: "Highland Dental Full Denture",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Full Denture",
},
{
id: "highland-partial-denture",
name: "Highland Dental Partial Denture",
labName: "Highland Dental Studio",
labPhone: "781-552-5198",
labAddress: "87 Cambridge Street, Burlington, MA 01803",
caseType: "Partial Denture",
},
];
+1 -13
View File
@@ -1120,22 +1120,10 @@ export default function AppointmentsPage() {
const patient = apt ? patientsFromDay.find((p) => p.id === apt.patientId) : null; const patient = apt ? patientsFromDay.find((p) => p.id === apt.patientId) : null;
const insuranceProvider = (patient as any)?.insuranceProvider as string | null ?? null; const insuranceProvider = (patient as any)?.insuranceProvider as string | null ?? null;
const p = insuranceProvider?.toLowerCase() ?? ""; const p = insuranceProvider?.toLowerCase() ?? "";
const isMassHealth = p.includes("masshealth");
let autoCheck = "other-providers"; let autoCheck = "other-providers";
if (isMassHealth) { if (p.includes("delta dental ma")) {
const dob = (patient as any)?.dateOfBirth;
let age: number | null = null;
if (dob) {
const dobDate = typeof dob === "string" ? new Date(dob) : dob as Date;
const today = new Date();
age = today.getFullYear() - dobDate.getFullYear();
const m = today.getMonth() - dobDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < dobDate.getDate())) age--;
}
autoCheck = age !== null && age >= 21 ? "mh-history" : "cmsp";
} else if (p.includes("delta dental ma")) {
autoCheck = "ddma"; autoCheck = "ddma";
} else if (p.includes("delta dental ins")) { } else if (p.includes("delta dental ins")) {
autoCheck = "delta-ins"; autoCheck = "delta-ins";
+1 -1
View File
@@ -88,7 +88,7 @@ export default function AuthPage() {
<Card className="p-8 bg-white"> <Card className="p-8 bg-white">
<div className="mb-10 text-center"> <div className="mb-10 text-center">
<h1 className="text-3xl font-bold text-primary mb-3 tracking-tight"> <h1 className="text-3xl font-bold text-primary mb-3 tracking-tight">
My Dental Office Management Wellspring Acupuncture Office Management
</h1> </h1>
<p className="text-sm font-semibold tracking-[0.15em] bg-clip-text text-transparent animate-gradient-flash" <p className="text-sm font-semibold tracking-[0.15em] bg-clip-text text-transparent animate-gradient-flash"
style={{ backgroundImage: "linear-gradient(90deg, #7c3aed, #d946ef, #06b6d4, #7c3aed)", backgroundSize: "200% 100%" }}> style={{ backgroundImage: "linear-gradient(90deg, #7c3aed, #d946ef, #06b6d4, #7c3aed)", backgroundSize: "200% 100%" }}>
+24 -13
View File
@@ -143,10 +143,12 @@ export default function ClaimsPage() {
// CCA result: pdfFileId is already saved by the processor — open preview directly // CCA result: pdfFileId is already saved by the processor — open preview directly
if (jobResult.pdfFileId) { if (jobResult.pdfFileId) {
advanceAiClaimQueue(); advanceAiClaimQueue();
advanceChatbotClaimQueue(); const advancedToNext = advanceChatbotClaimQueue();
setPreviewPdfId(jobResult.pdfFileId); if (!advancedToNext) {
setPreviewFallbackFilename(jobResult.pdfFilename ?? `cca_claim_${jobResult.claimNumber ?? "unknown"}.pdf`); setPreviewPdfId(jobResult.pdfFileId);
setPreviewOpen(true); setPreviewFallbackFilename(jobResult.pdfFilename ?? `cca_claim_${jobResult.claimNumber ?? "unknown"}.pdf`);
setPreviewOpen(true);
}
dispatch(setTaskStatus({ dispatch(setTaskStatus({
key: "claimSubmit", key: "claimSubmit",
status: "success", status: "success",
@@ -336,7 +338,9 @@ export default function ClaimsPage() {
// Advance the chatbot_claim_queue / chatbot_check_claim_queue after selenium PDF is saved. // Advance the chatbot_claim_queue / chatbot_check_claim_queue after selenium PDF is saved.
// NOT called from closeClaim — called only after the selenium job completes and PDF is downloaded. // NOT called from closeClaim — called only after the selenium job completes and PDF is downloaded.
const advanceChatbotClaimQueue = () => { // Returns true if it navigated on to another queued patient (caller should skip the PDF
// preview popup in that case — the form is about to be replaced by the next patient's claim).
const advanceChatbotClaimQueue = (): boolean => {
try { try {
const raw = sessionStorage.getItem("chatbot_check_claim_queue"); const raw = sessionStorage.getItem("chatbot_check_claim_queue");
if (raw) { if (raw) {
@@ -369,7 +373,7 @@ export default function ClaimsPage() {
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill")); window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
setWouterLocation("/insurance-status"); setWouterLocation("/insurance-status");
}, 500); }, 500);
return; return true;
} }
sessionStorage.removeItem("chatbot_check_claim_queue"); sessionStorage.removeItem("chatbot_check_claim_queue");
} }
@@ -403,11 +407,13 @@ export default function ClaimsPage() {
setTimeout(() => { setTimeout(() => {
setWouterLocation(`/claims?newPatient=${next.patient.id}`); setWouterLocation(`/claims?newPatient=${next.patient.id}`);
}, 500); }, 500);
return; return true;
} }
sessionStorage.removeItem("chatbot_claim_queue"); sessionStorage.removeItem("chatbot_claim_queue");
} }
} catch {} } catch {}
return false;
}; };
// Advance the ai_claim_queue by removing the first item (current patient). // Advance the ai_claim_queue by removing the first item (current patient).
@@ -447,10 +453,13 @@ export default function ClaimsPage() {
const data = await res.json(); const data = await res.json();
if (!res.ok) throw new Error(data?.message || "Failed to save claim"); if (!res.ok) throw new Error(data?.message || "Failed to save claim");
queryClient.invalidateQueries({ queryKey: QK_CLAIMS_BASE }); queryClient.invalidateQueries({ queryKey: QK_CLAIMS_BASE });
if (!isDraft) { // NOTE: advanceChatbotClaimQueue() is NOT called here. This function only creates
advanceAiClaimQueue(); // the claim DB record; for selenium-based insurances (MH/CCA/DDMA/Tufts/United) the
advanceChatbotClaimQueue(); // actual submission + PDF happens afterward. Advancing the queue here would move to
} // the next patient before the current patient's selenium job (and PDF) finishes.
// The queue is advanced later, once the selenium job completes (see
// handleMHSeleniumPdfDownload and the CCA branch in the job-status useEffect).
if (!isDraft) advanceAiClaimQueue();
return data; return data;
}; };
@@ -964,13 +973,15 @@ export default function ClaimsPage() {
duration: isPreAuth ? 10000 : 5000, duration: isPreAuth ? 10000 : 5000,
}); });
let advancedToNext = false;
if (!isPreAuth) { if (!isPreAuth) {
advanceAiClaimQueue(); advanceAiClaimQueue();
advanceChatbotClaimQueue(); advancedToNext = advanceChatbotClaimQueue();
} }
// Pop up the final PDF so the user doesn't need to navigate to Documents // Pop up the final PDF so the user doesn't need to navigate to Documents
if (result.pdfFileId) { // (skip if we're about to navigate to the next queued patient's claim)
if (result.pdfFileId && !advancedToNext) {
setPreviewPdfId(result.pdfFileId); setPreviewPdfId(result.pdfFileId);
setPreviewFallbackFilename(result.fileName ?? null); setPreviewFallbackFilename(result.fileName ?? null);
setPreviewOpen(true); setPreviewOpen(true);
@@ -0,0 +1,27 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { GraduationCap } from "lucide-react";
export default function DentalEducationPage() {
return (
<div className="container mx-auto space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Dental Education</h1>
<p className="text-muted-foreground">Educational resources for dental staff and patients.</p>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<GraduationCap className="h-5 w-5 text-lime-600" />
Coming Soon
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground text-sm">
Dental education content will be available here.
</p>
</CardContent>
</Card>
</div>
);
}
+81 -660
View File
@@ -28,13 +28,12 @@ import {
clearTaskStatus, clearTaskStatus,
} from "@/redux/slices/seleniumTaskSlice"; } from "@/redux/slices/seleniumTaskSlice";
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner"; import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; import { parseLocalDate } from "@/utils/dateUtils";
import { socket } from "@/lib/socket"; import { socket } from "@/lib/socket";
import { InsertPatient, Patient } from "@repo/db/types"; import { InsertPatient, Patient } from "@repo/db/types";
import { DateInput } from "@/components/ui/dateInput"; import { DateInput } from "@/components/ui/dateInput";
import { QK_PATIENTS_BASE } from "@/components/patients/patient-table"; import { QK_PATIENTS_BASE } from "@/components/patients/patient-table";
import { PdfPreviewModal } from "@/components/insurance-status/pdf-preview-modal"; import { PdfPreviewModal } from "@/components/insurance-status/pdf-preview-modal";
import { DualPdfPreviewModal } from "@/components/insurance-status/dual-pdf-preview-modal";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { DdmaEligibilityButton } from "@/components/insurance-status/ddma-buton-modal"; import { DdmaEligibilityButton } from "@/components/insurance-status/ddma-buton-modal";
import { DeltaInsEligibilityButton } from "@/components/insurance-status/deltains-button-modal"; import { DeltaInsEligibilityButton } from "@/components/insurance-status/deltains-button-modal";
@@ -44,6 +43,26 @@ import { CCAEligibilityButton } from "@/components/insurance-status/cca-button-m
import { BcbsMaEligibilityButton } from "@/components/insurance-status/bcbs-ma-button-modal"; import { BcbsMaEligibilityButton } from "@/components/insurance-status/bcbs-ma-button-modal";
import { useLicense } from "@/hooks/use-license"; import { useLicense } from "@/hooks/use-license";
const INSURANCE_PROVIDERS = [
"Aetna",
"BMC HealthNet Plan",
"CIGNA",
"Cenpatico Massachusetts",
"Commonwealth Care Alliance",
"Harvard Pilgrim Health Care",
"Humana",
"MA BCBS",
"MA Medicaid-MassHealth",
"Mass Advantage",
"Mass General Brigham Health Plan",
"Senior Whole Health of Massachusetts",
"Tricare East",
"Tufts Health Plan",
"UMR - Wausau",
"United Health Care",
"Wellcare",
];
/** /**
* Waits for a Selenium job to complete by racing socket.io events against * Waits for a Selenium job to complete by racing socket.io events against
* HTTP polling every 3 seconds. This ensures the result is received even * HTTP polling every 3 seconds. This ensures the result is received even
@@ -110,16 +129,8 @@ export default function InsuranceStatusPage() {
const [dateOfBirth, setDateOfBirth] = useState<Date | null>(null); const [dateOfBirth, setDateOfBirth] = useState<Date | null>(null);
const [firstName, setFirstName] = useState(""); const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState("");
const [insuranceProvider, setInsuranceProvider] = useState("");
const isFormIncomplete = !memberId || !dateOfBirth; const isFormIncomplete = !memberId || !dateOfBirth;
const [isCheckingEligibilityStatus, setIsCheckingEligibilityStatus] =
useState(false);
const [isCheckingEligibilityAppointment, setIsCheckingEligibilityAppointment] =
useState(false);
const [isCheckingEligibilityClaimsPreAuth, setIsCheckingEligibilityClaimsPreAuth] =
useState(false);
const [isCheckingEligibilityHistory, setIsCheckingEligibilityHistory] =
useState(false);
const [isCheckingCMSP, setIsCheckingCMSP] = useState(false);
// AI Call Insurance section // AI Call Insurance section
const [aiCallOpen, setAiCallOpen] = useState(false); const [aiCallOpen, setAiCallOpen] = useState(false);
@@ -147,27 +158,7 @@ export default function InsuranceStatusPage() {
string | null string | null
>(null); >(null);
// Dual PDF modal state (used by MH Eligibility & History) const pendingAutoCheck = useRef<"ddma" | "delta-ins" | "united-sco" | "tufts-sco" | "cca" | null>(null);
const [dualPreviewOpen, setDualPreviewOpen] = useState(false);
const [dualEligibilityPdfId, setDualEligibilityPdfId] = useState<number | null>(null);
const [dualEligibilityFilename, setDualEligibilityFilename] = useState<string | null>(null);
const [dualMemberDetailsPdfId, setDualMemberDetailsPdfId] = useState<number | null>(null);
const [dualMemberDetailsFilename, setDualMemberDetailsFilename] = useState<string | null>(null);
const [dualHistoryPdfId, setDualHistoryPdfId] = useState<number | null>(null);
const [dualHistoryFilename, setDualHistoryFilename] = useState<string | null>(null);
// CMSP PDF modal state (used by CMSP Eligibility & History & Remaining)
const [cmspPreviewOpen, setCmspPreviewOpen] = useState(false);
const [cmspEligibilityPdfId, setCmspEligibilityPdfId] = useState<number | null>(null);
const [cmspEligibilityFilename, setCmspEligibilityFilename] = useState<string | null>(null);
const [cmspMemberDetailsPdfId, setCmspMemberDetailsPdfId] = useState<number | null>(null);
const [cmspMemberDetailsFilename, setCmspMemberDetailsFilename] = useState<string | null>(null);
const [cmspHistoryPdfId, setCmspHistoryPdfId] = useState<number | null>(null);
const [cmspHistoryFilename, setCmspHistoryFilename] = useState<string | null>(null);
const [cmspAccumulatorPdfId, setCmspAccumulatorPdfId] = useState<number | null>(null);
const [cmspAccumulatorFilename, setCmspAccumulatorFilename] = useState<string | null>(null);
const pendingAutoCheck = useRef<"mh" | "mh-history" | "cmsp" | "ddma" | "delta-ins" | "united-sco" | "tufts-sco" | "cca" | null>(null);
const [triggerTarget, setTriggerTarget] = useState<string | null>(null); const [triggerTarget, setTriggerTarget] = useState<string | null>(null);
const pendingScrollTo = useRef<string | null>(null); const pendingScrollTo = useRef<string | null>(null);
const [prefillTick, setPrefillTick] = useState(0); const [prefillTick, setPrefillTick] = useState(0);
@@ -206,6 +197,11 @@ export default function InsuranceStatusPage() {
setMemberId(selectedPatient.insuranceId ?? ""); setMemberId(selectedPatient.insuranceId ?? "");
setFirstName(selectedPatient.firstName ?? ""); setFirstName(selectedPatient.firstName ?? "");
setLastName(selectedPatient.lastName ?? ""); setLastName(selectedPatient.lastName ?? "");
setInsuranceProvider(
INSURANCE_PROVIDERS.includes(selectedPatient.insuranceProvider ?? "")
? selectedPatient.insuranceProvider!
: "",
);
const dob = const dob =
typeof selectedPatient.dateOfBirth === "string" typeof selectedPatient.dateOfBirth === "string"
@@ -217,9 +213,34 @@ export default function InsuranceStatusPage() {
setFirstName(""); setFirstName("");
setLastName(""); setLastName("");
setDateOfBirth(null); setDateOfBirth(null);
setInsuranceProvider("");
} }
}, [selectedPatient]); }, [selectedPatient]);
// Save the chosen insurance provider on the patient record once an
// eligibility check succeeds, so it shows in the patient table.
const saveInsuranceProvider = async () => {
if (!insuranceProvider) return;
try {
let patientId = selectedPatient?.id ?? null;
if (!patientId && memberId) {
const res = await apiRequest(
"GET",
`/api/patients/by-insurance-id?insuranceId=${encodeURIComponent(memberId)}`,
);
if (res.ok) patientId = (await res.json())?.id ?? null;
}
if (!patientId) return;
const res = await apiRequest("PUT", `/api/patients/${patientId}`, {
insuranceProvider,
});
if (!res.ok) return;
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
} catch {
// non-critical: eligibility result is already available
}
};
// Auto-lookup patient by member ID when typed manually // Auto-lookup patient by member ID when typed manually
useEffect(() => { useEffect(() => {
if (selectedPatient && memberId === (selectedPatient.insuranceId ?? "")) return; if (selectedPatient && memberId === (selectedPatient.insuranceId ?? "")) return;
@@ -280,56 +301,6 @@ export default function InsuranceStatusPage() {
}, },
}); });
const isUnder14 = (dob: Date): boolean => {
const cutoff = new Date(dob);
cutoff.setFullYear(cutoff.getFullYear() + 14);
return new Date() < cutoff;
};
// Shared: run MH eligibility selenium job, return jobResult or throw
const runMHEligibilitySelenium = async (): Promise<any> => {
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
const data = {
memberId,
dateOfBirth: formattedDob,
insuranceSiteKey: "MH",
firstName: firstName || undefined,
lastName: lastName || undefined,
};
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "pending",
message: "Sending Data to Selenium...",
}),
);
const response = await apiRequest(
"POST",
"/api/insurance-status/eligibility-check",
{ data: data, socketId: socket.id },
);
const enqueueResult = await response.json();
if (enqueueResult.error) throw new Error(enqueueResult.error);
const jobId = enqueueResult.jobId;
if (!jobId) throw new Error("No jobId returned from server");
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "pending",
message: "Selenium browser starting...",
}),
);
return waitForSeleniumJob(jobId, (msg) =>
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
);
};
const handleAddPatient = async () => { const handleAddPatient = async () => {
const newPatient: InsertPatient = { const newPatient: InsertPatient = {
firstName, firstName,
@@ -343,347 +314,13 @@ export default function InsuranceStatusPage() {
await addPatientMutation.mutateAsync(newPatient); await addPatientMutation.mutateAsync(newPatient);
}; };
// MH Eligibility — check eligibility, save to DB, open PDF preview
const handleMHEligibilityButton = async () => {
if (!memberId || !dateOfBirth) {
toast({
title: "Missing Fields",
description: "Please fill in all the required fields: Member ID, Date of Birth.",
variant: "destructive",
});
return;
}
if (isUnder14(dateOfBirth)) {
toast({ title: "CMSP Patient", description: "Patient is under 14 — running CMSP Eligibility & History & Remaining instead.", variant: "default" });
return handleCMSPButton();
}
setIsCheckingEligibilityStatus(true);
try {
const jobResult = await runMHEligibilitySelenium();
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "success",
message: "Patient status is updated, and its eligibility pdf is uploaded at Document Page.",
}),
);
toast({
title: "Selenium service done.",
description: "Your Patient Eligibility is fetched and updated, Kindly search through the patient.",
variant: "default",
});
setSelectedPatient(null);
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
void tryAppointmentFromChatbot();
if (jobResult.pdfFileId) {
setPreviewPdfId(Number(jobResult.pdfFileId));
setPreviewFallbackFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
setPreviewOpen(true);
}
processNextInQueue();
} catch (error: any) {
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
} finally {
setIsCheckingEligibilityStatus(false);
}
};
// MH Eligibility & Appointment — check eligibility, save to DB, create appointment for today
const handleMHEligibilityAppointmentButton = async () => {
if (!memberId || !dateOfBirth) {
toast({
title: "Missing Fields",
description: "Please fill in all the required fields: Member ID, Date of Birth.",
variant: "destructive",
});
return;
}
setIsCheckingEligibilityAppointment(true);
try {
const jobResult = await runMHEligibilitySelenium();
const isInactive = jobResult?.patientUpdateStatus?.includes("INACTIVE");
if (isInactive) {
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "error",
message: "Insurance is inactive. No appointment created.",
}),
);
toast({
title: "Insurance Inactive",
description: "Patient insurance is inactive. No appointment was created.",
variant: "destructive",
});
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
return;
}
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
// Look up the patient (created/updated by the Selenium worker) then create today's appointment
let apptTime: string | null = null;
try {
const lookupRes = await apiRequest("GET", `/api/patients/by-insurance-id?insuranceId=${encodeURIComponent(memberId)}`);
if (lookupRes.ok) {
const patient = await lookupRes.json();
if (patient?.id) {
const apptRes = await apiRequest("POST", "/api/ai/create-appointment-today", { patientId: patient.id });
if (apptRes.ok) {
const apptData = await apptRes.json();
apptTime = apptData.startTime ? `${apptData.startTime} (${apptData.column ?? "Column A"})` : null;
}
}
}
} catch {}
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "success",
message: apptTime
? `Eligibility checked. Appointment created at ${apptTime}.`
: "Eligibility checked. Could not create appointment — please add manually.",
}),
);
toast({
title: "Eligibility checked.",
description: apptTime
? `Patient added to today's schedule at ${apptTime}.`
: "Eligibility saved. Could not create appointment — Column A may be full.",
variant: "default",
});
setSelectedPatient(null);
if (jobResult.pdfFileId) {
setPreviewPdfId(Number(jobResult.pdfFileId));
setPreviewFallbackFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
setPreviewOpen(true);
}
} catch (error: any) {
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
} finally {
setIsCheckingEligibilityAppointment(false);
}
};
// MH Eligibility & Claims/PreAuth — check eligibility, save to DB, navigate to claims
const handleMHEligibilityClaimsPreAuthButton = async () => {
if (!memberId || !dateOfBirth) {
toast({
title: "Missing Fields",
description: "Please fill in all the required fields: Member ID, Date of Birth.",
variant: "destructive",
});
return;
}
setIsCheckingEligibilityClaimsPreAuth(true);
try {
await runMHEligibilitySelenium();
dispatch(
setTaskStatus({
key: "eligibilityCheck",
status: "success",
message: "Eligibility checked and saved. Redirecting to Claims/PreAuth...",
}),
);
toast({
title: "Eligibility checked.",
description: "Patient eligibility saved. Redirecting to Claims/PreAuth.",
variant: "default",
});
setSelectedPatient(null);
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
setLocation("/claims");
} catch (error: any) {
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
} finally {
setIsCheckingEligibilityClaimsPreAuth(false);
}
};
// MH Eligibility & History — eligibility check + service history, saves both PDFs, auto-downloads eligibility PDF only
const handleMHEligibilityHistoryButton = async () => {
if (!memberId || !dateOfBirth) {
toast({
title: "Missing Fields",
description: "Please fill in all the required fields: Member ID, Date of Birth.",
variant: "destructive",
});
return;
}
if (isUnder14(dateOfBirth)) {
toast({ title: "CMSP Patient", description: "Patient is under 14 — running CMSP Eligibility & History & Remaining instead.", variant: "default" });
return handleCMSPButton();
}
setIsCheckingEligibilityHistory(true);
try {
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
const data = {
memberId,
dateOfBirth: formattedDob,
insuranceSiteKey: "MH",
firstName: firstName || undefined,
lastName: lastName || undefined,
};
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Sending Data to Selenium..." }));
const response = await apiRequest(
"POST",
"/api/insurance-status/eligibility-history-check",
{ data, socketId: socket.id },
);
const enqueueResult = await response.json();
if (enqueueResult.error) throw new Error(enqueueResult.error);
const jobId = enqueueResult.jobId;
if (!jobId) throw new Error("No jobId returned from server");
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." }));
const jobResult = await waitForSeleniumJob(jobId, (msg) =>
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
);
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "Eligibility and service history PDFs saved to Documents." }));
toast({
title: "Done",
description: "Eligibility and service history PDFs saved. Eligibility PDF downloading now.",
variant: "default",
});
const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot();
if (claimed) return;
setSelectedPatient(null);
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
processNextInQueue();
// Open all PDFs side by side in the modal
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId) {
setDualEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
setDualEligibilityFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
setDualMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
setDualMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `eligibility_member_details_${memberId}.pdf`);
setDualHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null);
setDualHistoryFilename(jobResult.historyPdfFilename ?? `eligibility_history_${memberId}.pdf`);
setDualPreviewOpen(true);
}
} catch (error: any) {
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
} finally {
setIsCheckingEligibilityHistory(false);
}
};
// CMSP Eligibility & History & Remaining — eligibility + service history + accumulator PDFs
const handleCMSPButton = async () => {
if (!memberId || !dateOfBirth) {
toast({
title: "Missing Fields",
description: "Please fill in Member ID and Date of Birth.",
variant: "destructive",
});
return;
}
setIsCheckingCMSP(true);
try {
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
const data = {
memberId,
dateOfBirth: formattedDob,
insuranceSiteKey: "MH",
firstName: firstName || undefined,
lastName: lastName || undefined,
};
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Sending Data to Selenium..." }));
const response = await apiRequest(
"POST",
"/api/insurance-status/cmsp-eligibility-history-remaining-check",
{ data, socketId: socket.id },
);
const enqueueResult = await response.json();
if (enqueueResult.error) throw new Error(enqueueResult.error);
const jobId = enqueueResult.jobId;
if (!jobId) throw new Error("No jobId returned from server");
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." }));
const jobResult = await waitForSeleniumJob(jobId, (msg) =>
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: msg }))
);
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "CMSP PDFs saved to Documents." }));
toast({
title: "Done",
description: "Eligibility, history, and accumulator PDFs saved.",
variant: "default",
});
setSelectedPatient(null);
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
processNextInQueue();
// Open 4-panel modal
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
setCmspEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`);
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`);
setCmspHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null);
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `cmsp_history_${memberId}.pdf`);
setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ? Number(jobResult.accumulatorPdfFileId) : null);
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `cmsp_accumulator_${memberId}.pdf`);
setCmspPreviewOpen(true);
}
} catch (error: any) {
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
} finally {
setIsCheckingCMSP(false);
}
};
// Auto-trigger after prefill (from chatbot or schedule page "Check Eligibility") // Auto-trigger after prefill (from chatbot or schedule page "Check Eligibility")
useEffect(() => { useEffect(() => {
if (!pendingAutoCheck.current || !memberId || !dateOfBirth) return; if (!pendingAutoCheck.current || !memberId || !dateOfBirth) return;
const check = pendingAutoCheck.current; const check = pendingAutoCheck.current;
pendingAutoCheck.current = null; pendingAutoCheck.current = null;
if (check === "mh" || check === "mh-history") { // Button-component-handled providers: pass target to their autoTrigger prop
if (isUnder14(dateOfBirth)) { setTriggerTarget(check);
handleCMSPButton();
} else {
handleMHEligibilityHistoryButton();
}
} else if (check === "cmsp") {
handleCMSPButton();
} else {
// Button-component-handled providers: pass target to their autoTrigger prop
setTriggerTarget(check);
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [memberId, dateOfBirth, prefillTick]); }, [memberId, dateOfBirth, prefillTick]);
@@ -820,7 +457,7 @@ export default function InsuranceStatusPage() {
const autoCheck = params.get("autoCheck") as typeof pendingAutoCheck.current; const autoCheck = params.get("autoCheck") as typeof pendingAutoCheck.current;
const scrollTo = params.get("scrollTo"); const scrollTo = params.get("scrollTo");
const knownAutoChecks = ["mh", "mh-history", "cmsp", "ddma", "delta-ins", "united-sco", "tufts-sco", "cca"] as const; const knownAutoChecks = ["ddma", "delta-ins", "united-sco", "tufts-sco", "cca"] as const;
if (autoCheck && (knownAutoChecks as readonly string[]).includes(autoCheck)) { if (autoCheck && (knownAutoChecks as readonly string[]).includes(autoCheck)) {
pendingAutoCheck.current = autoCheck; pendingAutoCheck.current = autoCheck;
} else if (scrollTo) { } else if (scrollTo) {
@@ -932,100 +569,25 @@ export default function InsuranceStatusPage() {
</div> </div>
</div> </div>
<div className="flex flex-col-2 gap-4"> <div className="grid grid-cols-4 md:grid-cols-4 gap-4 mb-4">
<Button <div className="space-y-2">
onClick={() => handleMHEligibilityButton()} <Label htmlFor="insuranceProvider">Insurance Provider</Label>
className="w-full" <Select
disabled={isCheckingEligibilityStatus} value={insuranceProvider}
> onValueChange={setInsuranceProvider}
{isCheckingEligibilityStatus ? ( >
<> <SelectTrigger id="insuranceProvider">
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" /> <SelectValue placeholder="Select insurance provider" />
Processing... </SelectTrigger>
</> <SelectContent>
) : ( {INSURANCE_PROVIDERS.map((name) => (
<> <SelectItem key={name} value={name}>
<CheckCircle className="h-4 w-4 mr-2" /> {name}
MH Eligibility </SelectItem>
</> ))}
)} </SelectContent>
</Button> </Select>
</div>
<Button
onClick={() => handleMHEligibilityAppointmentButton()}
className="w-full"
disabled={isCheckingEligibilityAppointment}
>
{isCheckingEligibilityAppointment ? (
<>
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
Processing...
</>
) : (
<>
<CheckCircle className="h-4 w-4 mr-2" />
MH Eligibility&Appointment
</>
)}
</Button>
<Button
onClick={() => handleMHEligibilityClaimsPreAuthButton()}
className="w-full"
disabled={isCheckingEligibilityClaimsPreAuth}
>
{isCheckingEligibilityClaimsPreAuth ? (
<>
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
Processing...
</>
) : (
<>
<CheckCircle className="h-4 w-4 mr-2" />
MH Eligibility&Claims/PreAuth
</>
)}
</Button>
</div>
<div className="flex flex-col-2 gap-4 mt-4">
<Button
id="eligibility-mh-history"
className="w-full"
disabled={isCheckingEligibilityHistory}
onClick={() => handleMHEligibilityHistoryButton()}
>
{isCheckingEligibilityHistory ? (
<>
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
Processing...
</>
) : (
<>
<CheckCircle className="h-4 w-4 mr-2" />
MH Eligibility &amp; History
</>
)}
</Button>
<Button
id="eligibility-cmsp"
className="w-full"
disabled={isCheckingCMSP}
onClick={() => handleCMSPButton()}
>
{isCheckingCMSP ? (
<>
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
Processing...
</>
) : (
<>
<CheckCircle className="h-4 w-4 mr-2" />
CMSP Eligibility &amp; History &amp; Remaining
</>
)}
</Button>
</div> </div>
{/* TEMP PROVIDER BUTTONS */} {/* TEMP PROVIDER BUTTONS */}
@@ -1046,6 +608,7 @@ export default function InsuranceStatusPage() {
autoTrigger={triggerTarget === "ddma"} autoTrigger={triggerTarget === "ddma"}
onAutoTriggered={() => setTriggerTarget(null)} onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
const claimed = await tryClaimFromChatbot(selectedPatient?.id); const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
if (!claimed) { if (!claimed) {
@@ -1068,6 +631,7 @@ export default function InsuranceStatusPage() {
autoTrigger={triggerTarget === "delta-ins"} autoTrigger={triggerTarget === "delta-ins"}
onAutoTriggered={() => setTriggerTarget(null)} onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
const claimed = await tryClaimFromChatbot(selectedPatient?.id); const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
if (!claimed) { if (!claimed) {
@@ -1088,6 +652,7 @@ export default function InsuranceStatusPage() {
lastName={lastName} lastName={lastName}
isFormIncomplete={isFormIncomplete} isFormIncomplete={isFormIncomplete}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
setPreviewPdfId(pdfId); setPreviewPdfId(pdfId);
setPreviewFallbackFilename( setPreviewFallbackFilename(
@@ -1112,6 +677,7 @@ export default function InsuranceStatusPage() {
autoTrigger={triggerTarget === "tufts-sco"} autoTrigger={triggerTarget === "tufts-sco"}
onAutoTriggered={() => setTriggerTarget(null)} onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
const claimed = await tryClaimFromChatbot(selectedPatient?.id); const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
if (!claimed) { if (!claimed) {
@@ -1134,6 +700,7 @@ export default function InsuranceStatusPage() {
autoTrigger={triggerTarget === "united-sco"} autoTrigger={triggerTarget === "united-sco"}
onAutoTriggered={() => setTriggerTarget(null)} onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
const claimed = await tryClaimFromChatbot(selectedPatient?.id); const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
if (!claimed) { if (!claimed) {
@@ -1156,6 +723,7 @@ export default function InsuranceStatusPage() {
autoTrigger={triggerTarget === "cca"} autoTrigger={triggerTarget === "cca"}
onAutoTriggered={() => setTriggerTarget(null)} onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => { onPdfReady={async (pdfId, fallbackFilename) => {
void saveInsuranceProvider();
const claimed = await tryClaimFromChatbot(selectedPatient?.id); const claimed = await tryClaimFromChatbot(selectedPatient?.id);
void tryAppointmentFromChatbot(); void tryAppointmentFromChatbot();
if (!claimed) { if (!claimed) {
@@ -1169,81 +737,6 @@ export default function InsuranceStatusPage() {
</div> </div>
</div> </div>
{/* Row 3 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
United AAPR
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Aetna
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Altus
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Metlife Dental
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Cigna
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Delta WA
</Button>
</div>
{/* Row 5 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Delta IL
</Button>
<Button
className="w-full"
variant="outline"
disabled={isFormIncomplete}
>
<CheckCircle className="h-4 w-4 mr-2" />
Others
</Button>
</div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -1394,78 +887,6 @@ export default function InsuranceStatusPage() {
autoDownload autoDownload
/> />
{/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */}
<DualPdfPreviewModal
open={cmspPreviewOpen}
onClose={() => {
setCmspPreviewOpen(false);
setCmspEligibilityPdfId(null);
setCmspEligibilityFilename(null);
setCmspMemberDetailsPdfId(null);
setCmspMemberDetailsFilename(null);
setCmspHistoryPdfId(null);
setCmspHistoryFilename(null);
setCmspAccumulatorPdfId(null);
setCmspAccumulatorFilename(null);
}}
title="CMSP Eligibility, History &amp; Remaining"
panels={[
{
pdfId: cmspEligibilityPdfId,
fallbackFilename: cmspEligibilityFilename,
label: "Eligibility",
autoDownload: true,
},
{
pdfId: cmspMemberDetailsPdfId,
fallbackFilename: cmspMemberDetailsFilename,
label: "Member Details",
},
{
pdfId: cmspHistoryPdfId,
fallbackFilename: cmspHistoryFilename,
label: "Service History",
},
{
pdfId: cmspAccumulatorPdfId,
fallbackFilename: cmspAccumulatorFilename,
label: "Accumulator (Remaining)",
},
]}
/>
{/* 3-panel modal for MH Eligibility & History */}
<DualPdfPreviewModal
open={dualPreviewOpen}
onClose={() => {
setDualPreviewOpen(false);
setDualEligibilityPdfId(null);
setDualEligibilityFilename(null);
setDualMemberDetailsPdfId(null);
setDualMemberDetailsFilename(null);
setDualHistoryPdfId(null);
setDualHistoryFilename(null);
}}
title="MH Eligibility &amp; Service History"
panels={[
{
pdfId: dualEligibilityPdfId,
fallbackFilename: dualEligibilityFilename,
label: "Eligibility",
autoDownload: true,
},
{
pdfId: dualMemberDetailsPdfId,
fallbackFilename: dualMemberDetailsFilename,
label: "Member Details",
},
{
pdfId: dualHistoryPdfId,
fallbackFilename: dualHistoryFilename,
label: "Service History",
},
]}
/>
</div> </div>
); );
} }
@@ -19,6 +19,7 @@ import { OfficeContactCard } from "@/components/settings/office-contact-card";
import { ProcedureTimeslotCard } from "@/components/settings/procedure-timeslot-card"; import { ProcedureTimeslotCard } from "@/components/settings/procedure-timeslot-card";
import { InsuranceContactCard } from "@/components/settings/insurance-contact-card"; import { InsuranceContactCard } from "@/components/settings/insurance-contact-card";
import { AiChatSettingsCard } from "@/components/settings/ai-chat-settings-card"; import { AiChatSettingsCard } from "@/components/settings/ai-chat-settings-card";
import { SeleniumSettingsCard } from "@/components/settings/selenium-settings-card";
export default function SettingsPage() { export default function SettingsPage() {
const { toast } = useToast(); const { toast } = useToast();
const { user: currentUser } = useAuth(); const { user: currentUser } = useAuth();
@@ -232,6 +233,8 @@ export default function SettingsPage() {
return <ProcedureTimeslotCard />; return <ProcedureTimeslotCard />;
case "insurancecontact": case "insurancecontact":
return <InsuranceContactCard />; return <InsuranceContactCard />;
case "selenium":
return <SeleniumSettingsCard />;
default: default:
return null; return null;
} }
+6 -1
View File
@@ -20,6 +20,7 @@ import { OfficeContactCard } from "@/components/settings/office-contact-card";
import { ProcedureTimeslotCard } from "@/components/settings/procedure-timeslot-card"; import { ProcedureTimeslotCard } from "@/components/settings/procedure-timeslot-card";
import { InsuranceContactCard } from "@/components/settings/insurance-contact-card"; import { InsuranceContactCard } from "@/components/settings/insurance-contact-card";
import { AiChatSettingsCard } from "@/components/settings/ai-chat-settings-card"; import { AiChatSettingsCard } from "@/components/settings/ai-chat-settings-card";
import { SeleniumSettingsCard } from "@/components/settings/selenium-settings-card";
type SectionId = type SectionId =
| "staff" | "staff"
@@ -34,7 +35,8 @@ type SectionId =
| "officehours" | "officehours"
| "officecontact" | "officecontact"
| "proceduretimeslot" | "proceduretimeslot"
| "insurancecontact"; | "insurancecontact"
| "selenium";
export default function SettingsPage() { export default function SettingsPage() {
const { toast } = useToast(); const { toast } = useToast();
@@ -276,6 +278,9 @@ export default function SettingsPage() {
case "insurancecontact": case "insurancecontact":
return <InsuranceContactCard />; return <InsuranceContactCard />;
case "selenium":
return <SeleniumSettingsCard />;
default: default:
return null; return null;
} }
+3 -82
View File
@@ -6,15 +6,13 @@ import uvicorn
import asyncio import asyncio
from selenium_claimSubmitWorker import AutomationMassHealthClaimsLogin from selenium_claimSubmitWorker import AutomationMassHealthClaimsLogin
from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck
from selenium_MH_eligibilityHistoryCheckWorker import AutomationMassHealthEligibilityHistoryCheck
from selenium_CMSP_eligibilityHistoryRemainingCheckWorker import AutomationCMSPEligibilityHistoryRemainingCheck
from selenium_claimStatusCheckWorker import AutomationMassHealthClaimStatusCheck from selenium_claimStatusCheckWorker import AutomationMassHealthClaimStatusCheck
from selenium_preAuthWorker import AutomationMassHealthPreAuth from selenium_preAuthWorker import AutomationMassHealthPreAuth
from selenium_MHSinglePaymentCheckWorker import AutomationMassHealthSinglePaymentCheck from selenium_MHSinglePaymentCheckWorker import AutomationMassHealthSinglePaymentCheck
from selenium_MHBatchPaymentCheckWorker import AutomationMassHealthBatchPaymentCheck from selenium_MHBatchPaymentCheckWorker import AutomationMassHealthBatchPaymentCheck
import os import os
import time import time
import helpers_ddma_eligibility as hddma import helpers_claimmd_eligibility as hddma
import helpers_deltains_eligibility as hdeltains import helpers_deltains_eligibility as hdeltains
import helpers_unitedsco_eligibility as hunitedsco import helpers_unitedsco_eligibility as hunitedsco
import helpers_dentaquest_eligibility as hdentaquest import helpers_dentaquest_eligibility as hdentaquest
@@ -29,7 +27,7 @@ import helpers_tuftssco_preauth as htuftssco_preauth
import helpers_bcbs_ma_eligibility as hbcbs_ma import helpers_bcbs_ma_eligibility as hbcbs_ma
# Import startup session-clear functions # Import startup session-clear functions
from ddma_browser_manager import clear_ddma_session_on_startup from claimmd_browser_manager import clear_ddma_session_on_startup
from deltains_browser_manager import clear_deltains_session_on_startup from deltains_browser_manager import clear_deltains_session_on_startup
from unitedsco_browser_manager import clear_unitedsco_session_on_startup from unitedsco_browser_manager import clear_unitedsco_session_on_startup
from dentaquest_browser_manager import clear_dentaquest_session_on_startup from dentaquest_browser_manager import clear_dentaquest_session_on_startup
@@ -146,83 +144,6 @@ async def start_workflow(request: Request):
async with lock: async with lock:
active_jobs -= 1 active_jobs -= 1
# Endpoint: 2a — MH Eligibility + Service History
@app.post("/mh-eligibility-history-check")
async def mh_eligibility_history_check(request: Request):
global active_jobs, waiting_jobs
data = await request.json()
async with lock:
waiting_jobs += 1
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try:
bot = AutomationMassHealthEligibilityHistoryCheck(data)
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
if result.get("status") == "error":
return {"status": "error", "message": result.get("message")}
port = os.getenv("PORT", "5002")
url_host = os.getenv("HOST", "localhost")
if result.get("pdf_path"):
filename = os.path.basename(result["pdf_path"])
result["pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
if result.get("history_pdf_path"):
filename = os.path.basename(result["history_pdf_path"])
result["history_pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
return result
except Exception as e:
return {"status": "error", "message": str(e)}
finally:
async with lock:
active_jobs -= 1
# Endpoint: 2b — CMSP Eligibility + Service History + Accumulator (Remaining)
@app.post("/cmsp-eligibility-history-remaining-check")
async def cmsp_eligibility_history_remaining_check(request: Request):
global active_jobs, waiting_jobs
data = await request.json()
async with lock:
waiting_jobs += 1
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try:
bot = AutomationCMSPEligibilityHistoryRemainingCheck(data)
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
if result.get("status") == "error":
return {"status": "error", "message": result.get("message")}
port = os.getenv("PORT", "5002")
url_host = os.getenv("HOST", "localhost")
for key, url_key in [
("pdf_path", "pdf_url"),
("history_pdf_path", "history_pdf_url"),
("accumulator_pdf_path", "accumulator_pdf_url"),
]:
if result.get(key):
filename = os.path.basename(result[key])
result[url_key] = f"http://{url_host}:{port}/downloads/{filename}"
return result
except Exception as e:
return {"status": "error", "message": str(e)}
finally:
async with lock:
active_jobs -= 1
# Endpoint: 2.1 — Start the automation for Claims login (open browser and log in) # Endpoint: 2.1 — Start the automation for Claims login (open browser and log in)
@app.post("/claims-login") @app.post("/claims-login")
async def start_claims_login(request: Request): async def start_claims_login(request: Request):
@@ -410,7 +331,7 @@ async def ddma_eligibility(request: Request):
waiting_jobs += 1 waiting_jobs += 1
# run in background (queued under semaphore) # run in background (queued under semaphore)
asyncio.create_task(_ddma_worker_wrapper(sid, data, url="https://providers.deltadentalma.com/onboarding/start/")) asyncio.create_task(_ddma_worker_wrapper(sid, data, url="https://www.deltadentalma.com/sign-in"))
return {"status": "started", "session_id": sid} return {"status": "started", "session_id": sid}
@@ -7,7 +7,7 @@ from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import WebDriverException, TimeoutException from selenium.common.exceptions import WebDriverException, TimeoutException
from selenium_DDMA_eligibilityCheckWorker import AutomationDeltaDentalMAEligibilityCheck from selenium_ClaimMD_eligibilityCheckWorker import AutomationDeltaDentalMAEligibilityCheck
# In-memory session store # In-memory session store
sessions: Dict[str, Dict[str, Any]] = {} sessions: Dict[str, Dict[str, Any]] = {}
@@ -146,13 +146,11 @@ async def start_ddma_run(sid: str, data: dict, url: str):
if otp_value: if otp_value:
print(f"[OTP poll {poll+1}] OTP received from app, typing it in...") print(f"[OTP poll {poll+1}] OTP received from app, typing it in...")
try: try:
otp_input = driver.find_element(By.XPATH, otp_input = driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
"//input[contains(@aria-label,'Verification') or contains(@placeholder,'verification') or @type='tel' or contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code')]"
)
otp_input.clear() otp_input.clear()
otp_input.send_keys(otp_value) otp_input.send_keys(otp_value)
try: try:
verify_btn = driver.find_element(By.XPATH, "//button[@type='button' and @aria-label='Verify']") verify_btn = driver.find_element(By.XPATH, "//button[@data-test-id='ConfirmSubmitButton']")
verify_btn.click() verify_btn.click()
except Exception: except Exception:
otp_input.send_keys("\n") otp_input.send_keys("\n")
@@ -169,7 +167,7 @@ async def start_ddma_run(sid: str, data: dict, url: str):
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url: if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
try: try:
member_search = WebDriverWait(driver, 5).until( member_search = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
print("[OTP] Member search found — login successful!") print("[OTP] Member search found — login successful!")
login_success = True login_success = True
@@ -179,16 +177,14 @@ async def start_ddma_run(sid: str, data: dict, url: str):
# Check if OTP input is still visible (user hasn't finished) # Check if OTP input is still visible (user hasn't finished)
try: try:
otp_input_elem = driver.find_element(By.XPATH, otp_input_elem = driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
"//input[contains(@aria-label,'Verification') or contains(@placeholder,'verification') or @type='tel']"
)
print(f"[OTP poll {poll+1}] OTP input still visible - waiting...") print(f"[OTP poll {poll+1}] OTP input still visible - waiting...")
except Exception: except Exception:
# OTP input gone — may mean login is completing; try members page # OTP input gone — may mean login is completing; try members page
if "onboarding" in current_url or "start" in current_url: if "sign-in" in current_url:
print("[OTP] OTP input gone, trying to navigate to members page...") print("[OTP] OTP input gone, trying to navigate to members page...")
try: try:
driver.get("https://providers.deltadentalma.com/members") driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(2) await asyncio.sleep(2)
except Exception: except Exception:
pass pass
@@ -200,10 +196,10 @@ async def start_ddma_run(sid: str, data: dict, url: str):
# Final check — navigate directly to members page # Final check — navigate directly to members page
try: try:
print("[OTP] Final attempt - navigating to members page...") print("[OTP] Final attempt - navigating to members page...")
driver.get("https://providers.deltadentalma.com/members") driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(3) await asyncio.sleep(3)
member_search = WebDriverWait(driver, 10).until( member_search = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
print("[OTP] Member search found — login successful!") print("[OTP] Member search found — login successful!")
login_success = True login_success = True
+3 -3
View File
@@ -8,7 +8,7 @@ from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import WebDriverException, TimeoutException from selenium.common.exceptions import WebDriverException, TimeoutException
from selenium_DDMA_claimSubmitWorker import AutomationDDMAClaimSubmit from selenium_DDMA_claimSubmitWorker import AutomationDDMAClaimSubmit
from ddma_browser_manager import get_browser_manager from claimmd_browser_manager import get_browser_manager
sessions: Dict[str, Dict[str, Any]] = {} sessions: Dict[str, Dict[str, Any]] = {}
@@ -56,7 +56,7 @@ async def _remove_session_later(sid: str, delay: int = 20):
async def start_ddma_claim_run(sid: str, data: dict, url: str): async def start_ddma_claim_run(sid: str, data: dict, url: str):
""" """
Run the DDMA claim workflow — mirrors helpers_ddma_eligibility exactly for Run the DDMA claim workflow — mirrors helpers_claimmd_eligibility exactly for
login/OTP, then continues into claim-specific steps. login/OTP, then continues into claim-specific steps.
OTP handling uses two complementary strategies: OTP handling uses two complementary strategies:
@@ -321,7 +321,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
# Close the browser window after claim (session preserved in profile) # Close the browser window after claim (session preserved in profile)
try: try:
from ddma_browser_manager import get_browser_manager from claimmd_browser_manager import get_browser_manager
get_browser_manager().quit_driver() get_browser_manager().quit_driver()
print("[DDMA Claim] Browser closed - session preserved in profile") print("[DDMA Claim] Browser closed - session preserved in profile")
except Exception as close_err: except Exception as close_err:
@@ -1,453 +0,0 @@
from selenium import webdriver
from selenium.common import TimeoutException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import time
import os
import base64
class AutomationCMSPEligibilityHistoryRemainingCheck:
def __init__(self, data):
self.headless = False
self.driver = None
self.extracted_data = {}
self.eligibility_tab = None
self.member_details_tab = None
self.accumulator_tab = None
self.data = data.get("data")
self.massdhp_username = self.data.get("massdhpUsername", "")
self.massdhp_password = self.data.get("massdhpPassword", "")
self.dateOfBirth = self.data.get("dateOfBirth", "")
self.memberId = self.data.get("memberId", "")
self.firstName = self.data.get("firstName", "")
self.lastName = self.data.get("lastName", "")
# Convert dateOfBirth from YYYY-MM-DD to MMDDYYYY
if self.dateOfBirth and "-" in self.dateOfBirth:
parts = self.dateOfBirth.split("-")
if len(parts) == 3:
year, month, day = parts
self.dateOfBirth = f"{month.zfill(2)}{day.zfill(2)}{year}"
self.download_dir = os.path.abspath("downloads")
os.makedirs(self.download_dir, exist_ok=True)
# ── driver ──────────────────────────────────────────────────────────────────
def config_driver(self):
options = webdriver.ChromeOptions()
if self.headless:
options.add_argument("--headless")
prefs = {
"download.default_directory": self.download_dir,
"plugins.always_open_pdf_externally": False,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
}
options.add_experimental_option("prefs", prefs)
s = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=s, options=options)
# ── login (same MassHealth credentials) ─────────────────────────────────────
def login(self):
wait = WebDriverWait(self.driver, 30)
try:
signin_button = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a.btn.btn-block.btn-primary[href='https://connectsso.masshealth-dental.org/mhprovider/index.html']")
)
)
signin_button.click()
time.sleep(3)
email_field = wait.until(EC.presence_of_element_located((By.ID, "User")))
email_field.clear()
email_field.send_keys(self.massdhp_username)
password_field = wait.until(EC.presence_of_element_located((By.ID, "Password")))
password_field.clear()
password_field.send_keys(self.massdhp_password)
login_button = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "input[type='submit'][name='submit'][value='Login']")
)
)
login_button.click()
return "Success"
except Exception as e:
print(f"[login] Error: {e}")
return "ERROR:LOGIN FAILED"
# ── step 1 — search member, extract eligibility data ────────────────────────
def step1(self):
wait = WebDriverWait(self.driver, 30)
substep = "init"
try:
substep = "patient_management"
patient_mgmt = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//strong[@translate='Patient Management']")
)
)
self.driver.execute_script("arguments[0].scrollIntoView(true);", patient_mgmt)
self.driver.execute_script("arguments[0].click();", patient_mgmt)
time.sleep(2)
substep = "member_eligibility_link"
eligibility_link = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//a[@translate='Member Eligibility']")
)
)
self.driver.execute_script("arguments[0].click();", eligibility_link)
time.sleep(2)
substep = "provider_dropdown"
provider_dropdown = wait.until(
EC.presence_of_element_located((By.NAME, "provider"))
)
select_provider = Select(provider_dropdown)
first_option = next(
(o for o in select_provider.options if o.get_attribute("value").strip()),
select_provider.options[0]
)
select_provider.select_by_value(first_option.get_attribute("value"))
time.sleep(2)
substep = "member_dob"
member_dob = wait.until(
EC.presence_of_all_elements_located((By.NAME, "dateInput"))
)[1]
member_dob.clear()
member_dob.send_keys(self.dateOfBirth)
substep = "member_number"
member_number = wait.until(
EC.presence_of_element_located((By.NAME, "memberNumber"))
)
member_number.clear()
member_number.send_keys(self.memberId)
substep = "search_button"
search_button = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(@class,'btn-primary')]"))
)
search_button.click()
substep = "wait_results"
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//h4[text()='Eligible' or text()='Ineligible']")
)
)
self.extracted_data = self._extract_data_from_page()
print(f"[step1] data extracted: {self.extracted_data}")
return "Success"
except Exception as e:
print(f"[step1] FAILED at substep='{substep}': {e}")
return f"ERROR:STEP1:{substep}"
# ── helpers ─────────────────────────────────────────────────────────────────
def _cell_text(self, cell):
text = cell.text.strip()
if not text:
try:
text = (self.driver.execute_script("return arguments[0].innerText;", cell) or "").strip()
except Exception:
pass
return text
def _normalize_id(self, s):
return "".join(c for c in str(s) if c.isalnum()).lower()
def _extract_data_from_page(self):
wait = WebDriverWait(self.driver, 15)
try:
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//h4[text()='Eligible' or text()='Ineligible']/following::table[1]/tbody/tr")
)
)
for status_label, elig_flag in [("Eligible", "Y"), ("Ineligible", "N")]:
rows = self.driver.find_elements(
By.XPATH,
f"//h4[text()='{status_label}']/following::table[1]/tbody/tr"
)
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) < 3:
continue
norm_cell = self._normalize_id(self._cell_text(cells[2]))
norm_self = self._normalize_id(self.memberId)
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
full_name = self._cell_text(cells[4]) if len(cells) > 4 else ""
plan_name = (
self._cell_text(cells[6]) if len(cells) > 6
else (self._cell_text(cells[-1]) if len(cells) > 4 else "")
)
name_parts = full_name.split()
return {
"eligibility": elig_flag,
"firstName": name_parts[0] if name_parts else "",
"lastName": " ".join(name_parts[1:]) if len(name_parts) > 1 else "",
"insurance": plan_name,
}
return {"eligibility": None}
except Exception as e:
print("Extraction error:", e)
return {"eligibility": None}
# ── step 2 — print eligibility PDF, return to results tab ───────────────────
def step2_eligibility_pdf(self):
wait = WebDriverWait(self.driver, 30)
self.eligibility_tab = self.driver.current_window_handle
try:
download_button = wait.until(
EC.element_to_be_clickable(
(By.XPATH, "//button[contains(.,'Printer Friendly Format')]")
)
)
download_button.click()
try:
WebDriverWait(self.driver, 10).until(lambda d: len(d.window_handles) > 1)
new_tabs = [t for t in self.driver.window_handles if t != self.eligibility_tab]
self.driver.switch_to.window(new_tabs[0])
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
time.sleep(2)
except TimeoutException:
print("No new tab for eligibility; printing current page directly")
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_eligibility_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("Eligibility PDF saved at:", pdf_path)
if self.driver.current_window_handle != self.eligibility_tab:
self.driver.close()
self.driver.switch_to.window(self.eligibility_tab)
time.sleep(1)
return pdf_path
except Exception as e:
print(f"[step2_eligibility_pdf] failed: {e}")
if self.eligibility_tab and self.driver.current_window_handle != self.eligibility_tab:
try:
self.driver.close()
self.driver.switch_to.window(self.eligibility_tab)
except Exception:
pass
raise
# ── step 3 — click member ID link → member details ──────────────────────────
def step3_click_member_id(self):
wait = WebDriverWait(self.driver, 30)
substep = "ng_bind_link"
try:
member_link = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a[ng-bind='member.memberNumber']")
)
)
self.driver.execute_script("arguments[0].click();", member_link)
time.sleep(2)
print(f"[step3] clicked member ID link, URL: {self.driver.current_url}")
return "Success"
except Exception as e:
print(f"[step3] FAILED at substep='{substep}': {e}")
return f"ERROR:STEP3:{substep}"
# ── step 3b — print member details page as PDF via CDP ───────────────────────
def step3b_member_details_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(2)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_member_details_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("Member details PDF saved at:", pdf_path)
return pdf_path
except Exception as e:
print(f"[step3b_member_details_pdf] failed: {e}")
return None
# ── step 4 — duplicate member details tab ───────────────────────────────────
# Opens the same member details URL in a new tab so that Tab A can navigate
# to service history while Tab B navigates to accumulator independently.
def step4_open_duplicate_tab(self):
try:
self.member_details_tab = self.driver.current_window_handle
member_details_url = self.driver.current_url
self.driver.execute_script("window.open(arguments[0]);", member_details_url)
time.sleep(3)
all_tabs = self.driver.window_handles
self.accumulator_tab = next(
t for t in all_tabs
if t not in [self.eligibility_tab, self.member_details_tab]
)
print(f"[step4] duplicate tab opened: {self.accumulator_tab}")
return "Success"
except Exception as e:
print(f"[step4] FAILED: {e}")
return f"ERROR:STEP4:open_duplicate_tab"
# ── step 5 — Tab A: service history → CDP print ──────────────────────────────
def step5_service_history_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
self.driver.switch_to.window(self.member_details_tab)
history_link = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a.btn.btn-primary[href*='service-history']")
)
)
self.driver.execute_script("arguments[0].scrollIntoView(true);", history_link)
self.driver.execute_script("arguments[0].click();", history_link)
time.sleep(2)
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(3)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_history_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("History PDF saved at:", pdf_path)
return pdf_path
except Exception as e:
print(f"[step5_service_history_pdf] failed: {e}")
return None
# ── step 6 — Tab B: accumulator → wait for data → CDP print ─────────────────
def step6_accumulator_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
self.driver.switch_to.window(self.accumulator_tab)
# Wait for the duplicate member details page to fully load
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(2)
# Click View Accumulator
accumulator_link = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a.btn.btn-primary[ng-if='vm.showAccumulator']")
)
)
self.driver.execute_script("arguments[0].scrollIntoView(true);", accumulator_link)
self.driver.execute_script("arguments[0].click();", accumulator_link)
time.sleep(2)
# Wait for accumulator page to load then print whatever is on screen —
# member info is always present even when there are no dollar amounts
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(4)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_accumulator_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("Accumulator PDF saved at:", pdf_path)
return pdf_path
except Exception as e:
print(f"[step6_accumulator_pdf] failed: {e}")
return None
# ── main workflow ────────────────────────────────────────────────────────────
def main_workflow(self, url):
try:
self.config_driver()
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
login_result = self.login()
if login_result.startswith("ERROR"):
return {"status": "error", "message": login_result}
step1_result = self.step1()
if step1_result.startswith("ERROR"):
return {"status": "error", "message": step1_result}
eligibility_pdf_path = self.step2_eligibility_pdf()
step3_result = self.step3_click_member_id()
if step3_result.startswith("ERROR"):
return {"status": "partial", "message": step3_result,
"pdf_path": eligibility_pdf_path, "file_type": "pdf"}
member_details_pdf_path = self.step3b_member_details_pdf()
# Duplicate the member details tab so service history and accumulator
# can be opened independently without back-navigation
step4_result = self.step4_open_duplicate_tab()
if step4_result.startswith("ERROR"):
return {"status": "partial", "message": step4_result,
"pdf_path": eligibility_pdf_path,
"member_details_pdf_path": member_details_pdf_path,
"file_type": "pdf"}
# Tab A: service history PDF
history_pdf_path = self.step5_service_history_pdf()
# Tab B: accumulator PDF
accumulator_pdf_path = self.step6_accumulator_pdf()
result = {
"status": "success",
"pdf_path": eligibility_pdf_path,
"member_details_pdf_path": member_details_pdf_path,
"history_pdf_path": history_pdf_path,
"accumulator_pdf_path": accumulator_pdf_path,
"file_type": "pdf",
"message": "Eligibility, member details, service history, and accumulator PDFs captured",
}
if self.extracted_data:
result.update(self.extracted_data)
return result
except Exception as e:
return {"status": "error", "message": str(e)}
finally:
if self.driver:
self.driver.quit()
@@ -10,8 +10,9 @@ import time
import os import os
import re import re
import base64 import base64
import calendar
from ddma_browser_manager import get_browser_manager from claimmd_browser_manager import get_browser_manager
class AutomationDeltaDentalMAEligibilityCheck: class AutomationDeltaDentalMAEligibilityCheck:
def __init__(self, data): def __init__(self, data):
@@ -45,7 +46,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
# Try to click logout button if visible # Try to click logout button if visible
try: try:
self.driver.get("https://providers.deltadentalma.com/") self.driver.get("https://www.deltadentalma.com/")
time.sleep(2) time.sleep(2)
logout_selectors = [ logout_selectors = [
@@ -99,27 +100,27 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url current_url = self.driver.current_url
print(f"[login] Current URL: {current_url}") print(f"[login] Current URL: {current_url}")
logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients"] logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients", "private/provider"]
is_logged_in_url = any(pattern in current_url.lower() for pattern in logged_in_patterns) is_logged_in_url = any(pattern in current_url.lower() for pattern in logged_in_patterns)
if is_logged_in_url and "onboarding" not in current_url.lower(): if is_logged_in_url and "sign-in" not in current_url.lower():
print("[login] Already on logged-in page - skipping login entirely") print("[login] Already on logged-in page - skipping login entirely")
if "member" not in current_url.lower(): if "member" not in current_url.lower():
try: try:
member_search = WebDriverWait(self.driver, 5).until( member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
print("[login] Found member search input - returning ALREADY_LOGGED_IN") print("[login] Found member search input - returning ALREADY_LOGGED_IN")
return "ALREADY_LOGGED_IN" return "ALREADY_LOGGED_IN"
except TimeoutException: except TimeoutException:
members_url = "https://providers.deltadentalma.com/members" members_url = "https://www.deltadentalma.com/private/provider/dashboard"
print(f"[login] Navigating to members page: {members_url}") print(f"[login] Navigating to members page: {members_url}")
self.driver.get(members_url) self.driver.get(members_url)
time.sleep(2) time.sleep(2)
try: try:
member_search = WebDriverWait(self.driver, 5).until( member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
print("[login] Member search found - ALREADY_LOGGED_IN") print("[login] Member search found - ALREADY_LOGGED_IN")
return "ALREADY_LOGGED_IN" return "ALREADY_LOGGED_IN"
@@ -137,9 +138,9 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url current_url = self.driver.current_url
print(f"[login] URL after navigation: {current_url}") print(f"[login] URL after navigation: {current_url}")
if "onboarding" not in current_url.lower(): if "sign-in" not in current_url.lower():
member_search = WebDriverWait(self.driver, 3).until( member_search = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
if member_search: if member_search:
print("[login] Session valid - skipping login") print("[login] Session valid - skipping login")
@@ -172,7 +173,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
try: try:
otp_candidate = WebDriverWait(self.driver, 10).until( otp_candidate = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located( EC.presence_of_element_located(
(By.XPATH, "//input[contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code') or contains(@aria-label,'Verification code')]") (By.XPATH, "//input[@data-test-id='MfaCode']")
) )
) )
if otp_candidate: if otp_candidate:
@@ -189,7 +190,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
time.sleep(2) time.sleep(2)
try: try:
member_search = WebDriverWait(self.driver, 5).until( member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
if member_search: if member_search:
print("[login] Already authenticated after modal dismiss") print("[login] Already authenticated after modal dismiss")
@@ -200,30 +201,21 @@ class AutomationDeltaDentalMAEligibilityCheck:
# Fill login form # Fill login form
try: try:
email_field = WebDriverWait(self.driver, 10).until( email_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//input[@name='username' and @type='text']")) EC.element_to_be_clickable((By.XPATH, "//input[@data-test-id='Username']"))
) )
except TimeoutException: except TimeoutException:
print("[login] Could not find login form - page may have changed") print("[login] Could not find login form - page may have changed")
return "ERROR: Login form not found" return "ERROR: Login form not found"
email_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='username' and @type='text']"))) email_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='Username']")))
email_field.clear() email_field.clear()
email_field.send_keys(self.massddma_username) email_field.send_keys(self.massddma_username)
password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='password' and @type='password']"))) password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='Password']")))
password_field.clear() password_field.clear()
password_field.send_keys(self.massddma_password) password_field.send_keys(self.massddma_password)
# Remember me login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@data-test-id='SigninSubmitButton']")))
try:
remember_me_checkbox = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//label[.//span[contains(text(),'Remember me')]]")
))
remember_me_checkbox.click()
except Exception:
print("[login] Remember me checkbox not found (continuing).")
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit' and @aria-label='Sign in']")))
login_button.click() login_button.click()
# Save credentials hash after login attempt # Save credentials hash after login attempt
@@ -234,7 +226,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
try: try:
otp_candidate = WebDriverWait(self.driver, 30).until( otp_candidate = WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located( EC.presence_of_element_located(
(By.XPATH, "//input[contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code')]") (By.XPATH, "//input[@data-test-id='MfaCode']")
) )
) )
if otp_candidate: if otp_candidate:
@@ -247,7 +239,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url.lower() current_url = self.driver.current_url.lower()
if "member" in current_url or "dashboard" in current_url: if "member" in current_url or "dashboard" in current_url:
member_search = WebDriverWait(self.driver, 5).until( member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]')) EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
) )
print("[login] Login successful - now on member search page") print("[login] Login successful - now on member search page")
return "SUCCESS" return "SUCCESS"
@@ -264,7 +256,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
except TimeoutException: except TimeoutException:
pass pass
if "onboarding" in self.driver.current_url.lower() or "login" in self.driver.current_url.lower(): if "sign-in" in self.driver.current_url.lower():
print("[login] Login failed - still on login page") print("[login] Login failed - still on login page")
return "ERROR:LOGIN FAILED: Still on login page" return "ERROR:LOGIN FAILED: Still on login page"
@@ -275,103 +267,66 @@ class AutomationDeltaDentalMAEligibilityCheck:
print("[login] Exception during login:", e) print("[login] Exception during login:", e)
return f"ERROR:LOGIN FAILED: {e}" return f"ERROR:LOGIN FAILED: {e}"
def _select_provider_dropdown(self):
"""
Click the provider dropdown on the member search page and select the option
matching self.providerName (case-insensitive). Falls back to the first option.
The button data-testid is 'member-search_provider_select-btn'.
"""
try:
short_wait = WebDriverWait(self.driver, 5)
try:
provider_btn = short_wait.until(
EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_provider_select-btn"]')
)
)
except TimeoutException:
print("[DDMA step1] No provider dropdown found — skipping")
return
provider_btn.click()
time.sleep(0.5)
try:
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//*[@role='option']"))
)
except TimeoutException:
print("[DDMA step1] Provider listbox did not open")
return
options = self.driver.find_elements(By.XPATH, "//*[@role='option']")
print(f"[DDMA step1] Provider options: {[o.text.strip() for o in options]}")
target = (self.providerName or "").strip().lower()
selected = False
if target:
for opt in options:
if target in opt.text.lower():
opt.click()
print(f"[DDMA step1] Selected provider: '{opt.text.strip()}'")
selected = True
break
if not selected and options:
options[0].click()
print(f"[DDMA step1] No match for '{self.providerName}', selected first: '{options[0].text.strip()}'")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA step1] Provider selection error (non-fatal): {e}")
def step1(self): def step1(self):
"""Fill search form with all available fields (flexible search).""" """Open the Group/Health Connector search page and search by Subscriber ID + Birthdate."""
wait = WebDriverWait(self.driver, 30) wait = WebDriverWait(self.driver, 30)
try: try:
fields = [] fields = []
if self.memberId: if self.memberId:
fields.append(f"ID: {self.memberId}") fields.append(f"SubscriberID: {self.memberId}")
if self.firstName:
fields.append(f"FirstName: {self.firstName}")
if self.lastName:
fields.append(f"LastName: {self.lastName}")
if self.dateOfBirth: if self.dateOfBirth:
fields.append(f"DOB: {self.dateOfBirth}") fields.append(f"DOB: {self.dateOfBirth}")
print(f"[DDMA step1] Starting search with: {', '.join(fields)}") print(f"[DDMA step1] Starting search with: {', '.join(fields)}")
# Wait for the search page to be fully loaded before interacting search_page_url = "https://www.deltadentalma.com/private/provider/check-patient-benefits-search"
wait.until(EC.presence_of_element_located(
(By.XPATH, '//input[@placeholder="Search by member ID"]')
))
time.sleep(1)
# Select provider from dropdown based on NPI settings # Try navigating straight to the search page first — avoids any click/visibility issues
self._select_provider_dropdown() print(f"[DDMA step1] Navigating directly to search page: {search_page_url}")
self.driver.get(search_page_url)
time.sleep(2)
def replace_with_sendkeys(el, value): if "check-patient-benefits-search" not in self.driver.current_url.lower():
el.click() # Direct deep-link didn't stick (e.g. bounced back to sign-in/dashboard) —
el.send_keys(Keys.CONTROL, "a") # fall back to clicking the Group/Health Connector link from the dashboard.
el.send_keys(Keys.BACKSPACE) print(f"[DDMA step1] Direct navigation redirected to: {self.driver.current_url} — falling back to clicking the link")
el.send_keys(value)
# 1. Fill Member ID if provided if "private/provider/dashboard" not in self.driver.current_url.lower():
if self.memberId: dashboard_url = "https://www.deltadentalma.com/private/provider/dashboard"
print(f"[DDMA step1] Navigating to dashboard: {dashboard_url}")
self.driver.get(dashboard_url)
time.sleep(2)
link_xpath = "//a[contains(@href, 'check-patient-benefits-search')]"
try: try:
member_id_input = wait.until(EC.presence_of_element_located( connector_link = WebDriverWait(self.driver, 20).until(
(By.XPATH, '//input[@placeholder="Search by member ID"]') EC.element_to_be_clickable((By.XPATH, link_xpath))
)) )
member_id_input.clear() connector_link.click()
member_id_input.send_keys(self.memberId) except TimeoutException:
print(f"[DDMA step1] Entered Member ID: {self.memberId}") # Element may exist but be hidden (e.g. a responsive duplicate) — try any match via JS click
time.sleep(0.2) candidates = self.driver.find_elements(By.XPATH, link_xpath)
except Exception as e: print(f"[DDMA step1] element_to_be_clickable timed out; found {len(candidates)} matching link(s) in DOM")
print(f"[DDMA step1] Warning: Could not fill Member ID: {e}") if candidates:
try:
self.driver.execute_script("arguments[0].click();", candidates[0])
except Exception as js_err:
print(f"[DDMA step1] JS click failed: {js_err}")
print(f"[DDMA step1] Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
else:
print(f"[DDMA step1] No Group/Health Connector link found in DOM. Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
# 2. Fill DOB if provided try:
wait.until(lambda d: "check-patient-benefits-search" in d.current_url)
except TimeoutException:
print(f"[DDMA step1] Click did not navigate to search page. Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
print(f"[DDMA step1] On Group/Health Connector search page: {self.driver.current_url}")
# Fill Birthdate (masked mm/dd/yyyy input — send digits only, mask inserts slashes)
if self.dateOfBirth: if self.dateOfBirth:
try: try:
dob_parts = self.dateOfBirth.split("-") dob_parts = self.dateOfBirth.split("-")
@@ -379,54 +334,57 @@ class AutomationDeltaDentalMAEligibilityCheck:
month = dob_parts[1].zfill(2) month = dob_parts[1].zfill(2)
day = dob_parts[2].zfill(2) day = dob_parts[2].zfill(2)
dob_container = wait.until( birthdate_input = wait.until(EC.element_to_be_clickable(
EC.presence_of_element_located( (By.XPATH, "//input[@data-test-id='InStateSearchBirthdate']")
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']") ))
) birthdate_input.click()
) birthdate_input.send_keys(Keys.CONTROL, "a")
birthdate_input.send_keys(Keys.BACKSPACE)
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']") birthdate_input.send_keys(f"{month}{day}{year}")
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']") print(f"[DDMA step1] Filled Birthdate: {month}/{day}/{year}")
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
replace_with_sendkeys(month_elem, month)
time.sleep(0.05)
replace_with_sendkeys(day_elem, day)
time.sleep(0.05)
replace_with_sendkeys(year_elem, year)
print(f"[DDMA step1] Filled DOB: {month}/{day}/{year}")
except Exception as e: except Exception as e:
print(f"[DDMA step1] Warning: Could not fill DOB: {e}") print(f"[DDMA step1] Warning: Could not fill Birthdate: {e}")
# Fill Subscriber ID (== member ID)
if self.memberId:
try:
subscriber_input = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//input[@data-test-id='InStateSearchSubscriberId']")
))
subscriber_input.clear()
subscriber_input.send_keys(self.memberId)
print(f"[DDMA step1] Filled Subscriber ID: {self.memberId}")
except Exception as e:
print(f"[DDMA step1] Warning: Could not fill Subscriber ID: {e}")
time.sleep(0.3) time.sleep(0.3)
# Click Search button # Click Search button
continue_btn = wait.until(EC.element_to_be_clickable( search_btn = wait.until(EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_search-button"]') (By.XPATH, "//button[@data-test-id='InStateSearchButton']")
)) ))
continue_btn.click() search_btn.click()
print("[DDMA step1] Clicked Search button") print("[DDMA step1] Clicked Search button")
# Wait for either results row or no-results message (up to 15s) # Wait for either the Members results table to render, or (when the
# Subscriber ID + DOB match exactly one family member) DDMA to skip
# the results list and navigate straight to the patient detail page.
# Note: the detail page URL is nested UNDER the search page path
# (".../check-patient-benefits-search/check-patient-benefit-details"),
# so "check-patient-benefits-search" alone can't distinguish them —
# only the detail page's own "-benefit-details" segment can.
try: try:
WebDriverWait(self.driver, 15).until( WebDriverWait(self.driver, 15).until(
EC.any_of( lambda d: d.find_elements(By.XPATH, "//table//tbody//tr")
EC.presence_of_element_located((By.XPATH, "//tbody//tr")), or "check-patient-benefit-details" in d.current_url.lower()
EC.presence_of_element_located((By.XPATH, '//div[@data-testid="member-search-result-no-results"]')),
)
) )
time.sleep(3) time.sleep(2)
except TimeoutException: except TimeoutException:
pass # proceed and let step2 handle missing results print("[DDMA step1] Warning: No results table appeared within timeout")
return "ERROR: INVALID SEARCH CRITERIA"
# Check for no-results error if "check-patient-benefit-details" in self.driver.current_url.lower():
try: print(f"[DDMA step1] Search navigated directly to detail page: {self.driver.current_url}")
error_msg = self.driver.find_element(By.XPATH, '//div[@data-testid="member-search-result-no-results"]')
if error_msg:
print("[DDMA step1] Error: No results found")
return "ERROR: INVALID SEARCH CRITERIA"
except Exception:
pass
print("[DDMA step1] Search completed successfully") print("[DDMA step1] Search completed successfully")
return "Success" return "Success"
@@ -442,112 +400,120 @@ class AutomationDeltaDentalMAEligibilityCheck:
try: try:
import re import re
# Wait for results table, then pause for full render
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2) # Let the row content fully render after table appears
except TimeoutException:
print("[DDMA step2] Warning: Results table not found within timeout")
eligibilityText = "unknown" eligibilityText = "unknown"
foundMemberId = self.memberId or "" foundMemberId = self.memberId or ""
patientName = "" # If the caller already knows this patient (e.g. an existing patient record
# being rechecked), use that name directly — no need to scrape/parse it off
# the DDMA page, only the eligibility status is actually needed.
patientName = f"{self.firstName} {self.lastName}".strip() if self.firstName and self.lastName else ""
known_patient_name = bool(patientName)
# Extract patient name from search results row # DDMA now sometimes skips the family-members results table entirely and
try: # navigates straight to the single-patient detail page (when Subscriber ID +
first_row = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]") # DOB match exactly one person). In that case there's no members table to
row_text = first_row.text.strip() # parse here — skip straight to the detail-page fallback extraction below.
print(f"[DDMA step2] First row text: {row_text[:150]}...") already_on_detail_page = "check-patient-benefit-details" in self.driver.current_url.lower()
lines = row_text.split('\n') if row_text else [] rows = []
for line in lines: target_row = None
line = line.strip()
if not line: if not already_on_detail_page:
continue # Wait for results table, then pause for full render
if re.match(r"^[A-Za-z\s\-']{2,60}$", line): try:
patientName = line WebDriverWait(self.driver, 10).until(
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'") EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2) # Let the row content fully render after table appears
except TimeoutException:
print("[DDMA step2] Warning: Results table not found within timeout")
# The Members table lists the whole family sharing this subscriber ID —
# find the row matching this patient's DOB (falls back to last name, then first row).
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
else:
print(f"[DDMA step2] Already on detail page: {self.driver.current_url}")
if self.dateOfBirth and rows:
try:
dob_parts = self.dateOfBirth.split("-")
dob_year, dob_month, dob_day = dob_parts[0], int(dob_parts[1]), int(dob_parts[2])
expected_dob = f"{calendar.month_abbr[dob_month]} {dob_day}, {dob_year}".lower()
for row in rows:
cells = row.find_elements(By.XPATH, "./td")
if len(cells) >= 3 and cells[2].text.strip().lower() == expected_dob:
target_row = row
break
except Exception as e:
print(f"[DDMA step2] DOB matching error (non-fatal): {e}")
if target_row is None and self.lastName and rows:
for row in rows:
if self.lastName.strip().lower() in row.text.lower():
target_row = row
break break
except Exception as e:
print(f"[DDMA step2] Error reading first row: {e}")
if target_row is None and rows:
target_row = rows[0]
print("[DDMA step2] No DOB/name match — defaulting to first family member row")
if target_row is not None:
try:
cells = target_row.find_elements(By.XPATH, "./td")
if cells and not known_patient_name:
patientName = cells[0].text.strip()
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
if len(cells) >= 2 and cells[1].text.strip():
foundMemberId = cells[1].text.strip()
if len(cells) >= 7:
status_text = cells[6].text.strip().lower()
if "inactive" in status_text or "termed" in status_text:
eligibilityText = "inactive"
elif "active" in status_text:
eligibilityText = "active"
else:
eligibilityText = status_text
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
except Exception as e:
print(f"[DDMA step2] Error reading matched row cells: {e}")
# Click into the member's detail page
try:
name_link = target_row.find_element(By.XPATH, ".//a")
name_link.click()
print(f"[DDMA step2] Clicked into detail page for: {patientName}")
except Exception as e:
print(f"[DDMA step2] Warning: Could not click patient name link: {e}")
else:
print("[DDMA step2] Warning: No family member rows found in search results")
# Wait for the detail page to load (URL gains the "-benefit-details" segment)
try: try:
short_wait = WebDriverWait(self.driver, 3) WebDriverWait(self.driver, 15).until(
status_link = short_wait.until(EC.presence_of_element_located(( lambda d: "check-patient-benefit-details" in d.current_url.lower()
By.XPATH, )
"(//tbody//tr)[1]//a[contains(@href, 'member-eligibility-search')]" print(f"[DDMA step2] Navigated to detail page: {self.driver.current_url}")
)))
eligibilityText = status_link.text.strip().lower()
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
except Exception: except Exception:
try: print(f"[DDMA step2] Warning: Still on search page: {self.driver.current_url}")
alt_status = self.driver.find_element(By.XPATH, "//*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]")
eligibilityText = alt_status.text.strip().lower()
if "active" in eligibilityText or "eligible" in eligibilityText:
eligibilityText = "active"
elif "inactive" in eligibilityText:
eligibilityText = "inactive"
print(f"[DDMA step2] Found eligibility via alternative: {eligibilityText}")
except Exception:
pass
# Find the member-details URL from the first row # Wait for meaningful content on the detail page
print("[DDMA step2] Looking for patient detail link...") for selector in [
detail_url = None "//*[contains(text(),'Date of Birth') or contains(text(),'Address') or contains(text(),'Member ID')]",
"//table", "//h1", "//h2",
link_selectors = [ ]:
"(//table//tbody//tr)[1]//td[1]//a",
"(//tbody//tr)[1]//a[contains(@href, 'member-details')]",
"(//tbody//tr)[1]//a[contains(@href, 'member')]",
"//a[contains(@href, 'member-details')]",
]
for selector in link_selectors:
try: try:
link_el = WebDriverWait(self.driver, 5).until( WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, selector)) EC.presence_of_element_located((By.XPATH, selector))
) )
href = link_el.get_attribute("href") print(f"[DDMA step2] Detail page content loaded: {selector}")
if href and "member-details" in href: break
detail_url = href
print(f"[DDMA step2] Found detail URL: {href}")
break
except Exception: except Exception:
continue continue
if detail_url: time.sleep(3) # Let JavaScript finish rendering all sections
# Always navigate via driver.get() — this blocks until the new page loads,
# unlike click() which returns immediately and causes a race condition.
print(f"[DDMA step2] Navigating to detail page: {detail_url}")
self.driver.get(detail_url)
# Confirm we actually landed on the detail page (not redirected away) # Fallback: refine patient name from the detail page text if row extraction failed
try: if not patientName:
WebDriverWait(self.driver, 15).until(
lambda d: "member-details" in d.current_url
)
print(f"[DDMA step2] Confirmed on detail page: {self.driver.current_url}")
except Exception:
print(f"[DDMA step2] Warning: URL after navigation: {self.driver.current_url}")
# Wait for meaningful content on the detail page
for selector in [
"//*[contains(text(),'Date of Birth') or contains(text(),'Address') or contains(text(),'Member ID')]",
"//table", "//h1", "//h2",
]:
try:
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, selector))
)
print(f"[DDMA step2] Detail page content loaded: {selector}")
break
except Exception:
continue
time.sleep(3) # Let JavaScript finish rendering all sections
# Get full page text as lines
try: try:
page_lines = self.driver.execute_script( page_lines = self.driver.execute_script(
"return document.body.innerText;" "return document.body.innerText;"
@@ -576,15 +542,29 @@ class AutomationDeltaDentalMAEligibilityCheck:
return False return False
return True return True
# Preferred: the "Benefits for JOSHUA ZHANG" heading names the actual
# patient. "Subscriber name:" is the policy holder (e.g. a parent), so
# it is only used below if this heading is missing.
for line in page_lines:
heading = re.match(r'^benefits\s+for\s+(.+)$', line, re.IGNORECASE)
if heading and looks_like_name(heading.group(1)):
patientName = heading.group(1).strip()
print(f"[DDMA step2] Extracted name from 'Benefits for' heading: '{patientName}'")
break
# Scan lines for the "Name" label. When "Name" appears alone we scan # Scan lines for the "Name" label. When "Name" appears alone we scan
# forward past accessibility text to the first plausible name value. # forward past accessibility text to the first plausible name value.
for i, line in enumerate(page_lines): for i, line in enumerate(page_lines):
if patientName:
break
# Case 1: "Name : Value" or "Name: Value" on the same line # Case 1: "Name : Value" or "Name: Value" on the same line
# (DDMA's detail page labels this "Subscriber name:", so that
# prefix must be accepted rather than excluded)
same_line = re.match( same_line = re.match(
r'^(?:member\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE r'^(?:member\s+|subscriber\s+)?name\s*[:\-]\s*(.+)$', line, re.IGNORECASE
) )
if same_line and not re.search( if same_line and not re.search(
r'(provider|group|subscriber|plan)\s+name', line, re.IGNORECASE r'(provider|group|plan)\s+name', line, re.IGNORECASE
): ):
candidate = same_line.group(1).strip() candidate = same_line.group(1).strip()
if looks_like_name(candidate): if looks_like_name(candidate):
@@ -593,7 +573,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
break break
# Case 2: "Name" or "Name:" alone on a line — scan forward for value # Case 2: "Name" or "Name:" alone on a line — scan forward for value
elif re.match(r'^(?:member\s+)?name\s*:?\s*$', line, re.IGNORECASE): elif re.match(r'^(?:member\s+|subscriber\s+)?name\s*:?\s*$', line, re.IGNORECASE):
for j in range(i + 1, min(i + 6, len(page_lines))): for j in range(i + 1, min(i + 6, len(page_lines))):
candidate = page_lines[j].strip() candidate = page_lines[j].strip()
if looks_like_name(candidate): if looks_like_name(candidate):
@@ -602,8 +582,6 @@ class AutomationDeltaDentalMAEligibilityCheck:
break break
if patientName: if patientName:
break break
else:
print("[DDMA step2] Warning: Could not find patient detail link")
# Clean patient name — strip any remaining date artifacts # Clean patient name — strip any remaining date artifacts
if patientName: if patientName:
@@ -614,6 +592,27 @@ class AutomationDeltaDentalMAEligibilityCheck:
if not patientName: if not patientName:
print("[DDMA step2] Could not extract patient name from detail page") print("[DDMA step2] Could not extract patient name from detail page")
# Fallback: refine eligibility status from the detail page's "Coverage is:"
# label if the family-table row extraction didn't set it (e.g. DDMA
# navigated straight to the detail page, skipping the members table).
if eligibilityText == "unknown":
try:
page_text = self.driver.execute_script("return document.body.innerText;")
coverage_match = re.search(
r'coverage\s+is\s*[:\-]?\s*([A-Za-z]+)', page_text, re.IGNORECASE
)
if coverage_match:
status_text = coverage_match.group(1).strip().lower()
if "inactive" in status_text or "termed" in status_text:
eligibilityText = "inactive"
elif "active" in status_text:
eligibilityText = "active"
else:
eligibilityText = status_text
print(f"[DDMA step2] Extracted eligibility from 'Coverage is:' label: '{eligibilityText}'")
except Exception as e:
print(f"[DDMA step2] Could not extract eligibility from 'Coverage is:' label: {e}")
# Wait for the page to be fully ready before capturing PDF # Wait for the page to be fully ready before capturing PDF
try: try:
WebDriverWait(self.driver, 30).until( WebDriverWait(self.driver, 30).until(
@@ -650,14 +649,6 @@ class AutomationDeltaDentalMAEligibilityCheck:
print(f"[DDMA step2] PDF saved at: {pdf_path}") print(f"[DDMA step2] PDF saved at: {pdf_path}")
# Close the browser window after PDF (session preserved in profile)
try:
from ddma_browser_manager import get_browser_manager
get_browser_manager().quit_driver()
print("[step2] Browser closed - session preserved in profile")
except Exception as e:
print(f"[step2] Error closing browser: {e}")
print(f"[DDMA step2] Final — PatientName: '{patientName}', MemberID: '{foundMemberId}'") print(f"[DDMA step2] Final — PatientName: '{patientName}', MemberID: '{foundMemberId}'")
return { return {
@@ -11,7 +11,7 @@ import json
import base64 import base64
from datetime import date from datetime import date
from ddma_browser_manager import get_browser_manager from claimmd_browser_manager import get_browser_manager
MEMBERS_URL = "https://providers.deltadentalma.com/members" MEMBERS_URL = "https://providers.deltadentalma.com/members"
@@ -1,471 +0,0 @@
from selenium import webdriver
from selenium.common import TimeoutException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import time
import os
import base64
class AutomationMassHealthEligibilityHistoryCheck:
def __init__(self, data):
self.headless = False
self.driver = None
self.extracted_data = {}
self.eligibility_tab = None # handle to the eligibility results tab
self.data = data.get("data")
self.massdhp_username = self.data.get("massdhpUsername", "")
self.massdhp_password = self.data.get("massdhpPassword", "")
self.dateOfBirth = self.data.get("dateOfBirth", "")
self.memberId = self.data.get("memberId", "")
self.firstName = self.data.get("firstName", "")
self.lastName = self.data.get("lastName", "")
# Convert dateOfBirth from YYYY-MM-DD to MMDDYYYY
if self.dateOfBirth and "-" in self.dateOfBirth:
parts = self.dateOfBirth.split("-")
if len(parts) == 3:
year, month, day = parts
self.dateOfBirth = f"{month.zfill(2)}{day.zfill(2)}{year}"
self.download_dir = os.path.abspath("downloads")
os.makedirs(self.download_dir, exist_ok=True)
# ── driver ──────────────────────────────────────────────────────────────────
def config_driver(self):
options = webdriver.ChromeOptions()
if self.headless:
options.add_argument("--headless")
prefs = {
"download.default_directory": self.download_dir,
"plugins.always_open_pdf_externally": False,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
}
options.add_experimental_option("prefs", prefs)
s = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=s, options=options)
# ── login ───────────────────────────────────────────────────────────────────
def login(self):
wait = WebDriverWait(self.driver, 30)
try:
signin_button = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a.btn.btn-block.btn-primary[href='https://connectsso.masshealth-dental.org/mhprovider/index.html']")
)
)
signin_button.click()
time.sleep(3)
email_field = wait.until(EC.presence_of_element_located((By.ID, "User")))
email_field.clear()
email_field.send_keys(self.massdhp_username)
password_field = wait.until(EC.presence_of_element_located((By.ID, "Password")))
password_field.clear()
password_field.send_keys(self.massdhp_password)
login_button = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "input[type='submit'][name='submit'][value='Login']")
)
)
login_button.click()
# Wait for the SSO redirect. On some machines Chrome opens the dashboard
# in a new tab and closes the SSO tab — poll ALL window handles.
print("[login] Waiting for SSO redirect to provider.masshealth-dental.org ...")
deadline = time.time() + 30
found = False
while time.time() < deadline:
time.sleep(0.5)
for handle in self.driver.window_handles:
try:
self.driver.switch_to.window(handle)
if self.driver.current_url.startswith("https://provider.masshealth-dental.org"):
print(f"[login] Redirect complete. URL: {self.driver.current_url}")
found = True
break
except Exception:
continue
if found:
break
if not found:
print("[login] Redirect timeout — portal window not found in any tab.")
return "ERROR: Login redirect timed out — check credentials or portal availability."
return "Success"
except Exception as e:
print(f"[login] Error: {e}")
return "ERROR:LOGIN FAILED"
# ── step 1 — search member, extract eligibility data ────────────────────────
def step1(self):
wait = WebDriverWait(self.driver, 30)
substep = "init"
try:
print(f"[step1] URL: {self.driver.current_url}")
substep = "patient_management"
patient_mgmt = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//strong[@translate='Patient Management']")
)
)
self.driver.execute_script("arguments[0].scrollIntoView(true);", patient_mgmt)
self.driver.execute_script("arguments[0].click();", patient_mgmt)
time.sleep(2)
substep = "member_eligibility_link"
eligibility_link = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//a[@translate='Member Eligibility']")
)
)
self.driver.execute_script("arguments[0].click();", eligibility_link)
time.sleep(2)
substep = "provider_dropdown"
provider_dropdown = wait.until(
EC.presence_of_element_located((By.NAME, "provider"))
)
select_provider = Select(provider_dropdown)
options = [o.text for o in select_provider.options]
print(f"[step1] provider options: {options}")
substep = "select_provider"
first_option = next(
(o for o in select_provider.options if o.get_attribute("value").strip()),
select_provider.options[0]
)
print(f"[step1] selecting provider: '{first_option.text}'")
select_provider.select_by_value(first_option.get_attribute("value"))
time.sleep(2)
substep = "member_dob"
member_dob = wait.until(
EC.presence_of_all_elements_located((By.NAME, "dateInput"))
)[1]
member_dob.clear()
member_dob.send_keys(self.dateOfBirth)
substep = "member_number"
member_number = wait.until(
EC.presence_of_element_located((By.NAME, "memberNumber"))
)
member_number.clear()
member_number.send_keys(self.memberId)
substep = "search_button"
search_button = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(@class,'btn-primary')]"))
)
search_button.click()
substep = "wait_results"
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//h4[text()='Eligible' or text()='Ineligible']")
)
)
self.extracted_data = self._extract_data_from_page()
print(f"[step1] data extracted: {self.extracted_data}")
return "Success"
except Exception as e:
print(f"[step1] FAILED at substep='{substep}': {e}")
print(f"[step1] URL at failure: {self.driver.current_url}")
return f"ERROR:STEP1:{substep}"
# ── helpers ─────────────────────────────────────────────────────────────────
def _cell_text(self, cell):
text = cell.text.strip()
if not text:
try:
text = (self.driver.execute_script("return arguments[0].innerText;", cell) or "").strip()
except Exception:
pass
return text
def _normalize_id(self, s):
return "".join(c for c in str(s) if c.isalnum()).lower()
def _extract_data_from_page(self):
wait = WebDriverWait(self.driver, 15)
extracted = {}
try:
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//h4[text()='Eligible' or text()='Ineligible']/following::table[1]/tbody/tr")
)
)
# AngularJS populates cell text after elements appear in the DOM.
# Retry a few times until a row with non-empty cell[2] is found.
for attempt in range(4):
for status_label, elig_flag in [("Eligible", "Y"), ("Ineligible", "N")]:
rows = self.driver.find_elements(
By.XPATH,
f"//h4[text()='{status_label}']/following::table[1]/tbody/tr"
)
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) < 3:
continue
member_number = self._cell_text(cells[2])
norm_cell = self._normalize_id(member_number)
norm_self = self._normalize_id(self.memberId)
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
full_name = self._cell_text(cells[4]) if len(cells) > 4 else ""
plan_name = (
self._cell_text(cells[6]) if len(cells) > 6
else (self._cell_text(cells[-1]) if len(cells) > 4 else "")
)
name_parts = full_name.split()
extracted = {
"eligibility": elig_flag,
"firstName": name_parts[0] if name_parts else "",
"lastName": " ".join(name_parts[1:]) if len(name_parts) > 1 else "",
"insurance": plan_name,
}
print(f"[extraction] MATCHED {status_label} → name='{full_name}' plan='{plan_name}'")
return extracted
if attempt < 3:
print(f"[extraction] cells not yet populated (attempt {attempt + 1}), retrying...")
time.sleep(1.5)
print(f"[extraction] No matching row for memberId='{self.memberId}'")
return {"eligibility": None}
except Exception as e:
print("Extraction error:", e)
return {"eligibility": None}
# ── step 2 — print eligibility PDF, stay on results tab ─────────────────────
def step2_eligibility_pdf(self):
wait = WebDriverWait(self.driver, 30)
self.eligibility_tab = self.driver.current_window_handle
try:
download_button = wait.until(
EC.element_to_be_clickable(
(By.XPATH, "//button[contains(.,'Printer Friendly Format')]")
)
)
download_button.click()
try:
WebDriverWait(self.driver, 10).until(lambda d: len(d.window_handles) > 1)
new_tabs = [t for t in self.driver.window_handles if t != self.eligibility_tab]
self.driver.switch_to.window(new_tabs[0])
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
time.sleep(2)
print("Printer-friendly tab opened:", self.driver.current_url)
except TimeoutException:
print("No new tab for eligibility; printing current page directly")
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"eligibility_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("Eligibility PDF saved at:", pdf_path)
# Close printer-friendly tab and return to eligibility results
if self.driver.current_window_handle != self.eligibility_tab:
self.driver.close()
self.driver.switch_to.window(self.eligibility_tab)
time.sleep(1)
return pdf_path
except Exception as e:
print(f"[step2_eligibility_pdf] failed: {e}")
# Ensure we are back on the eligibility results tab even on error
if self.eligibility_tab and self.driver.current_window_handle != self.eligibility_tab:
try:
self.driver.close()
self.driver.switch_to.window(self.eligibility_tab)
except Exception:
pass
raise
# ── step 3 — click member ID link → member details page ─────────────────────
def step3_click_member_id(self):
wait = WebDriverWait(self.driver, 30)
substep = "init"
try:
substep = "ng_bind_link"
member_link = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a[ng-bind='member.memberNumber']")
)
)
self.driver.execute_script("arguments[0].click();", member_link)
time.sleep(2)
print(f"[step3] clicked member ID link, URL: {self.driver.current_url}")
return "Success"
except Exception as e:
print(f"[step3] FAILED at substep='{substep}': {e}")
return f"ERROR:STEP3:{substep}"
# ── step 3b — print member details page as PDF via CDP ───────────────────────
def step3b_member_details_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(2)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"eligibility_member_details_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("Member details PDF saved at:", pdf_path)
return pdf_path
except Exception as e:
print(f"[step3b_member_details_pdf] failed: {e}")
return None
# ── step 4 — click "View Service History" on member details page ─────────────
def step4_view_service_history(self):
wait = WebDriverWait(self.driver, 30)
substep = "init"
try:
# Primary: href contains /service-history and has btn-primary class
substep = "service_history_link"
history_link = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "a.btn.btn-primary[href*='service-history']")
)
)
self.driver.execute_script("arguments[0].scrollIntoView(true);", history_link)
self.driver.execute_script("arguments[0].click();", history_link)
time.sleep(2)
print(f"[step4] navigated to service history, URL: {self.driver.current_url}")
return "Success"
except Exception as e:
print(f"[step4] FAILED at substep='{substep}': {e}")
return f"ERROR:STEP4:{substep}"
# ── step 5 — print service history PDF ──────────────────────────────────────
def step5_history_pdf(self):
wait = WebDriverWait(self.driver, 30)
current_tab = self.driver.current_window_handle
try:
print_button = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, "button.btn.btn-primary[ng-click='vm.printPage()']")
)
)
print_button.click()
try:
WebDriverWait(self.driver, 10).until(lambda d: len(d.window_handles) > 1)
new_tabs = [t for t in self.driver.window_handles if t != current_tab]
self.driver.switch_to.window(new_tabs[0])
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
time.sleep(2)
print("History printer-friendly tab opened:", self.driver.current_url)
except TimeoutException:
print("No new tab for history; printing current page directly")
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"eligibility_history_{safe_member}.pdf"
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True})
pdf_bytes = base64.b64decode(pdf_data["data"])
pdf_path = os.path.join(self.download_dir, pdf_filename)
with open(pdf_path, "wb") as f:
f.write(pdf_bytes)
print("History PDF saved at:", pdf_path)
return pdf_path
except Exception as e:
print(f"[step5_history_pdf] failed: {e}")
return None
# ── main workflow ────────────────────────────────────────────────────────────
def main_workflow(self, url):
try:
self.config_driver()
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
login_result = self.login()
if login_result.startswith("ERROR"):
return {"status": "error", "message": login_result}
step1_result = self.step1()
if step1_result.startswith("ERROR"):
return {"status": "error", "message": step1_result}
# Print eligibility PDF; stays on results tab afterwards
eligibility_pdf_path = self.step2_eligibility_pdf()
# Click the member ID link → member details
step3_result = self.step3_click_member_id()
if step3_result.startswith("ERROR"):
return {
"status": "partial",
"message": step3_result,
"pdf_path": eligibility_pdf_path,
"file_type": "pdf",
}
# Print member details page
member_details_pdf_path = self.step3b_member_details_pdf()
# Click "View Service History"
step4_result = self.step4_view_service_history()
if step4_result.startswith("ERROR"):
return {
"status": "partial",
"message": step4_result,
"pdf_path": eligibility_pdf_path,
"member_details_pdf_path": member_details_pdf_path,
"file_type": "pdf",
}
# Print history PDF
history_pdf_path = self.step5_history_pdf()
result = {
"status": "success",
"pdf_path": eligibility_pdf_path,
"member_details_pdf_path": member_details_pdf_path,
"history_pdf_path": history_pdf_path,
"file_type": "pdf",
"message": "Eligibility, member details, and service history PDFs captured",
}
if self.extracted_data:
result.update(self.extracted_data)
return result
except Exception as e:
return {"status": "error", "message": str(e)}
finally:
if self.driver:
self.driver.quit()
@@ -52,6 +52,7 @@ class AutomationUnitedDHClaimSubmit:
self.uniteddh_username = claim.get("uniteddhUsername", "") self.uniteddh_username = claim.get("uniteddhUsername", "")
self.uniteddh_password = claim.get("uniteddhPassword", "") self.uniteddh_password = claim.get("uniteddhPassword", "")
self.payment_group_id = claim.get("paymentGroupId", "")
self.download_dir = get_browser_manager().download_dir self.download_dir = get_browser_manager().download_dir
os.makedirs(self.download_dir, exist_ok=True) os.makedirs(self.download_dir, exist_ok=True)
@@ -595,11 +596,11 @@ class AutomationUnitedDHClaimSubmit:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedDH Claim] step1: Selected Treatment Location: Summit Dental Care") print(f"[UnitedDH Claim] step1: Selected Treatment Location: {self.payment_group_id}")
location_selected = True location_selected = True
except TimeoutException: except TimeoutException:
try: try:
@@ -632,11 +633,11 @@ class AutomationUnitedDHClaimSubmit:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedDH Claim] step1: Selected Billing Entity: Summit Dental Care") print(f"[UnitedDH Claim] step1: Selected Billing Entity: {self.payment_group_id}")
billing_selected = True billing_selected = True
except TimeoutException: except TimeoutException:
try: try:
@@ -52,6 +52,7 @@ class AutomationUnitedDHPreAuth:
self.uniteddh_username = claim.get("uniteddhUsername", "") self.uniteddh_username = claim.get("uniteddhUsername", "")
self.uniteddh_password = claim.get("uniteddhPassword", "") self.uniteddh_password = claim.get("uniteddhPassword", "")
self.payment_group_id = claim.get("paymentGroupId", "")
self.download_dir = get_browser_manager().download_dir self.download_dir = get_browser_manager().download_dir
os.makedirs(self.download_dir, exist_ok=True) os.makedirs(self.download_dir, exist_ok=True)
@@ -554,11 +555,11 @@ class AutomationUnitedDHPreAuth:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedDH PreAuth] step1: Selected Treatment Location: Summit Dental Care") print(f"[UnitedDH PreAuth] step1: Selected Treatment Location: {self.payment_group_id}")
location_selected = True location_selected = True
except TimeoutException: except TimeoutException:
try: try:
@@ -590,11 +591,11 @@ class AutomationUnitedDHPreAuth:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedDH PreAuth] step1: Selected Billing Entity: Summit Dental Care") print(f"[UnitedDH PreAuth] step1: Selected Billing Entity: {self.payment_group_id}")
billing_selected = True billing_selected = True
except TimeoutException: except TimeoutException:
try: try:
@@ -27,6 +27,7 @@ class AutomationUnitedSCOEligibilityCheck:
self.lastName = self.data.get("lastName", "") self.lastName = self.data.get("lastName", "")
self.unitedsco_username = self.data.get("unitedscoUsername", "") self.unitedsco_username = self.data.get("unitedscoUsername", "")
self.unitedsco_password = self.data.get("unitedscoPassword", "") self.unitedsco_password = self.data.get("unitedscoPassword", "")
self.payment_group_id = self.data.get("paymentGroupId", "")
# Use browser manager's download dir # Use browser manager's download dir
self.download_dir = get_browser_manager().download_dir self.download_dir = get_browser_manager().download_dir
@@ -653,11 +654,11 @@ class AutomationUnitedSCOEligibilityCheck:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedSCO step1] Selected Treatment Location: Summit Dental Care") print(f"[UnitedSCO step1] Selected Treatment Location: {self.payment_group_id}")
location_selected = True location_selected = True
except TimeoutException: except TimeoutException:
try: try:
@@ -691,11 +692,11 @@ class AutomationUnitedSCOEligibilityCheck:
try: try:
summit_option = WebDriverWait(self.driver, 5).until( summit_option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, EC.element_to_be_clickable((By.XPATH,
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]" f"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'{self.payment_group_id}')]"
)) ))
) )
summit_option.click() summit_option.click()
print("[UnitedSCO step1] Selected Billing Entity: Summit Dental Care") print(f"[UnitedSCO step1] Selected Billing Entity: {self.payment_group_id}")
billing_selected = True billing_selected = True
except TimeoutException: except TimeoutException:
try: try:
+3 -3
View File
@@ -71,7 +71,7 @@ echo ""
echo "==============================" echo "=============================="
echo " STEP 13 — Desktop shortcuts " echo " STEP 13 — Desktop shortcuts "
echo "==============================" echo "=============================="
bash ~/Desktop/DentalManagementMH06/setup-desktop-shortcut.sh bash ~/Desktop/Wellspringmanagement01/setup-desktop-shortcut.sh
echo "✓ Desktop shortcuts created" echo "✓ Desktop shortcuts created"
echo "" echo ""
@@ -79,6 +79,6 @@ echo "============================================"
echo " All steps complete!" echo " All steps complete!"
echo "" echo ""
echo " STEP 12 — To run the app, open 2 terminals:" echo " STEP 12 — To run the app, open 2 terminals:"
echo " Terminal 1: cd ~/Desktop/DentalManagementMH06 && npm run dev" echo " Terminal 1: cd ~/Desktop/Wellspringmanagement01 && npm run dev"
echo " Terminal 2: cd ~/Desktop/DentalManagementMH06/apps/SeleniumService && .venv/bin/python3 agent.py" echo " Terminal 2: cd ~/Desktop/Wellspringmanagement01/apps/SeleniumService && .venv/bin/python3 agent.py"
echo "============================================" echo "============================================"
+29 -34
View File
@@ -142,6 +142,7 @@
"pdfjs-dist": "^3.11.174", "pdfjs-dist": "^3.11.174",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"react": "^19.1.0", "react": "^19.1.0",
"react-barcode": "^1.6.1",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-day-picker": "9.7.0", "react-day-picker": "9.7.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
@@ -274,7 +275,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -559,7 +559,8 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",
"integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/@chevrotain/cst-dts-gen": { "node_modules/@chevrotain/cst-dts-gen": {
"version": "10.5.0", "version": "10.5.0",
@@ -628,8 +629,7 @@
"version": "0.3.15", "version": "0.3.15",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
"integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==",
"license": "Apache-2.0", "license": "Apache-2.0"
"peer": true
}, },
"node_modules/@electric-sql/pglite-socket": { "node_modules/@electric-sql/pglite-socket": {
"version": "0.0.20", "version": "0.0.20",
@@ -5013,7 +5013,6 @@
"integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"hoist-non-react-statics": "^3.3.0" "hoist-non-react-statics": "^3.3.0"
}, },
@@ -5066,7 +5065,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
} }
@@ -5131,7 +5129,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -5142,7 +5139,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@@ -5281,7 +5277,6 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1", "@typescript-eslint/types": "8.56.1",
@@ -5590,7 +5585,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -6136,7 +6130,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -7265,8 +7258,7 @@
"version": "8.6.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/embla-carousel-react": { "node_modules/embla-carousel-react": {
"version": "8.6.0", "version": "8.6.0",
@@ -7541,7 +7533,6 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -8765,7 +8756,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
"integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=16.9.0" "node": ">=16.9.0"
} }
@@ -9139,6 +9129,12 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/jsbarcode": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.12.3.tgz",
"integrity": "sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg==",
"license": "MIT"
},
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -9290,6 +9286,7 @@
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.6.0.tgz", "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.6.0.tgz",
"integrity": "sha512-GGaj5IMRfLv2HXXFzGk9diISMYLTpSTh6fzCZGKxWYW/NqEztIFtnXLq6G/RVhzFRmCykLap1fuC67LVKoQLcg==", "integrity": "sha512-GGaj5IMRfLv2HXXFzGk9diISMYLTpSTh6fzCZGKxWYW/NqEztIFtnXLq6G/RVhzFRmCykLap1fuC67LVKoQLcg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"p-queue": "6.6.2" "p-queue": "6.6.2"
}, },
@@ -10141,6 +10138,7 @@
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"mustache": "bin/mustache" "mustache": "bin/mustache"
} }
@@ -10485,7 +10483,6 @@
"resolved": "https://registry.npmjs.org/openai/-/openai-6.44.0.tgz", "resolved": "https://registry.npmjs.org/openai/-/openai-6.44.0.tgz",
"integrity": "sha512-09/gH+8jH0RgUwsgWHAaxsKGRT5zVZ95IaJUnqAWj6XejIBmnFRwq2WUIF37VtDEsmGrtPmvCs5+yBSeZGWvkA==", "integrity": "sha512-09/gH+8jH0RgUwsgWHAaxsKGRT5zVZ95IaJUnqAWj6XejIBmnFRwq2WUIF37VtDEsmGrtPmvCs5+yBSeZGWvkA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"peerDependencies": { "peerDependencies": {
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.25 || ^4.0" "zod": "^3.25 || ^4.0"
@@ -10563,6 +10560,7 @@
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"eventemitter3": "^4.0.4", "eventemitter3": "^4.0.4",
"p-timeout": "^3.2.0" "p-timeout": "^3.2.0"
@@ -10594,6 +10592,7 @@
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"p-finally": "^1.0.0" "p-finally": "^1.0.0"
}, },
@@ -10768,7 +10767,6 @@
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz",
"integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@@ -10801,7 +10799,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"pg-connection-string": "^2.11.0", "pg-connection-string": "^2.11.0",
"pg-pool": "^3.11.0", "pg-pool": "^3.11.0",
@@ -10966,7 +10963,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -11196,7 +11192,6 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -11889,11 +11884,23 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-barcode": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/react-barcode/-/react-barcode-1.6.1.tgz",
"integrity": "sha512-pc4ftnO5syHa/UjCruEeRsomlhoxKSugIgTA8T4dH0fvc89UMHL+/1Sp25IAphqG44pJkE5hMXhv89iS09jQyw==",
"license": "ISC",
"dependencies": {
"jsbarcode": "^3.8.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "16 - 19"
}
},
"node_modules/react-contexify": { "node_modules/react-contexify": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz", "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-6.0.0.tgz",
@@ -11981,7 +11988,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -11994,7 +12000,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
"integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
@@ -12026,7 +12031,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/use-sync-external-store": "^0.0.6", "@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0" "use-sync-external-store": "^1.4.0"
@@ -12307,8 +12311,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/redux-thunk": { "node_modules/redux-thunk": {
"version": "3.1.0", "version": "3.1.0",
@@ -13240,7 +13243,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -13534,7 +13536,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -13812,7 +13813,6 @@
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "~0.27.0", "esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5" "get-tsconfig": "^4.7.5"
@@ -13994,7 +13994,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -14285,7 +14284,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",
@@ -14834,7 +14832,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -15073,7 +15070,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@@ -15196,7 +15192,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
File diff suppressed because one or more lines are too long
+26 -1
View File
@@ -500,6 +500,23 @@ exports.Prisma.PatientConversationScalarFieldEnum = {
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
}; };
exports.Prisma.LabRxTemplateScalarFieldEnum = {
id: 'id',
userId: 'userId',
name: 'name',
labName: 'labName',
labPhone: 'labPhone',
labFax: 'labFax',
labAddress: 'labAddress',
labAccount: 'labAccount',
caseType: 'caseType',
material: 'material',
instructions: 'instructions',
sortOrder: 'sortOrder',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CommissionBatchScalarFieldEnum = { exports.Prisma.CommissionBatchScalarFieldEnum = {
id: 'id', id: 'id',
npiProviderId: 'npiProviderId', npiProviderId: 'npiProviderId',
@@ -516,6 +533,12 @@ exports.Prisma.CommissionBatchItemScalarFieldEnum = {
collectionAmount: 'collectionAmount' collectionAmount: 'collectionAmount'
}; };
exports.Prisma.SeleniumSettingsScalarFieldEnum = {
id: 'id',
userId: 'userId',
paymentGroupId: 'paymentGroupId'
};
exports.Prisma.SortOrder = { exports.Prisma.SortOrder = {
asc: 'asc', asc: 'asc',
desc: 'desc' desc: 'desc'
@@ -667,8 +690,10 @@ exports.Prisma.ModelName = {
InsuranceContact: 'InsuranceContact', InsuranceContact: 'InsuranceContact',
ProcedureTimeslot: 'ProcedureTimeslot', ProcedureTimeslot: 'ProcedureTimeslot',
PatientConversation: 'PatientConversation', PatientConversation: 'PatientConversation',
LabRxTemplate: 'LabRxTemplate',
CommissionBatch: 'CommissionBatch', CommissionBatch: 'CommissionBatch',
CommissionBatchItem: 'CommissionBatchItem' CommissionBatchItem: 'CommissionBatchItem',
SeleniumSettings: 'SeleniumSettings'
}; };
/** /**
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "prisma-client-d351a8e2afa5a626b25c24fc325b9f8c2c9b0ee5bb0f800b83ea538e43d12368", "name": "prisma-client-1397903f012032ff276557e7a077db07df8a7cae6f68fef0b7224d110a42b0e3",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"browser": "default.js", "browser": "default.js",
@@ -50,6 +50,8 @@ model User {
procedureTimeslot ProcedureTimeslot? procedureTimeslot ProcedureTimeslot?
insuranceContacts InsuranceContact[] insuranceContacts InsuranceContact[]
patientConversations PatientConversation[] patientConversations PatientConversation[]
labRxTemplates LabRxTemplate[]
seleniumSettings SeleniumSettings?
} }
model Patient { model Patient {
@@ -694,6 +696,28 @@ model PatientConversation {
@@map("patient_conversation") @@map("patient_conversation")
} }
model LabRxTemplate {
id Int @id @default(autoincrement())
userId Int
name String
labName String?
labPhone String?
labFax String?
labAddress String?
labAccount String?
caseType String?
material String?
instructions String?
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("lab_rx_template")
}
// Commission tracking // Commission tracking
model CommissionBatch { model CommissionBatch {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
@@ -721,3 +745,13 @@ model CommissionBatchItem {
@@unique([commissionBatchId, paymentId]) @@unique([commissionBatchId, paymentId])
@@index([paymentId]) @@index([paymentId])
} }
model SeleniumSettings {
id Int @id @default(autoincrement())
userId Int @unique
paymentGroupId String @default("")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("selenium_settings")
}
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "lab_rx_template" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"labName" TEXT,
"labPhone" TEXT,
"labFax" TEXT,
"labAddress" TEXT,
"labAccount" TEXT,
"caseType" TEXT,
"material" TEXT,
"instructions" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "lab_rx_template_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "lab_rx_template_userId_idx" ON "lab_rx_template"("userId");
-- AddForeignKey
ALTER TABLE "lab_rx_template" ADD CONSTRAINT "lab_rx_template_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "lab_rx_template" ADD COLUMN "sortOrder" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "lab_rx_template" ADD COLUMN "doctorName" TEXT;
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "selenium_settings" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"paymentGroupId" TEXT NOT NULL DEFAULT '',
CONSTRAINT "selenium_settings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "selenium_settings_userId_key" ON "selenium_settings"("userId");
-- AddForeignKey
ALTER TABLE "selenium_settings" ADD CONSTRAINT "selenium_settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,16 @@
/*
Warnings:
- You are about to drop the column `doctorName` on the `lab_rx_template` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Patient" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "User" ADD COLUMN "autoMhCheckDayOfWeek" INTEGER NOT NULL DEFAULT 1,
ADD COLUMN "autoMhCheckEnabled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "autoMhCheckHour" INTEGER NOT NULL DEFAULT 13;
-- AlterTable
ALTER TABLE "lab_rx_template" DROP COLUMN "doctorName";
+34
View File
@@ -50,6 +50,8 @@ model User {
procedureTimeslot ProcedureTimeslot? procedureTimeslot ProcedureTimeslot?
insuranceContacts InsuranceContact[] insuranceContacts InsuranceContact[]
patientConversations PatientConversation[] patientConversations PatientConversation[]
labRxTemplates LabRxTemplate[]
seleniumSettings SeleniumSettings?
} }
model Patient { model Patient {
@@ -695,6 +697,28 @@ model PatientConversation {
@@map("patient_conversation") @@map("patient_conversation")
} }
model LabRxTemplate {
id Int @id @default(autoincrement())
userId Int
name String
labName String?
labPhone String?
labFax String?
labAddress String?
labAccount String?
caseType String?
material String?
instructions String?
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("lab_rx_template")
}
// Commission tracking // Commission tracking
model CommissionBatch { model CommissionBatch {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
@@ -722,3 +746,13 @@ model CommissionBatchItem {
@@unique([commissionBatchId, paymentId]) @@unique([commissionBatchId, paymentId])
@@index([paymentId]) @@index([paymentId])
} }
model SeleniumSettings {
id Int @id @default(autoincrement())
userId Int @unique
paymentGroupId String @default("")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("selenium_settings")
}
@@ -1,8 +1,8 @@
{ {
"version": "1.0", "version": "1.0",
"generatorVersion": "1.0.0", "generatorVersion": "1.0.0",
"generatedAt": "2026-06-29T02:05:45.074Z", "generatedAt": "2026-09-22T04:11:49.276Z",
"outputPath": "/home/gg/Desktop/DentalManagementMH06/packages/db/shared", "outputPath": "/home/kk/Desktop/DentalManagementMH06/packages/db/shared",
"files": [ "files": [
"schemas/enums/TransactionIsolationLevel.schema.ts", "schemas/enums/TransactionIsolationLevel.schema.ts",
"schemas/enums/UserScalarFieldEnum.schema.ts", "schemas/enums/UserScalarFieldEnum.schema.ts",
@@ -37,8 +37,10 @@
"schemas/enums/InsuranceContactScalarFieldEnum.schema.ts", "schemas/enums/InsuranceContactScalarFieldEnum.schema.ts",
"schemas/enums/ProcedureTimeslotScalarFieldEnum.schema.ts", "schemas/enums/ProcedureTimeslotScalarFieldEnum.schema.ts",
"schemas/enums/PatientConversationScalarFieldEnum.schema.ts", "schemas/enums/PatientConversationScalarFieldEnum.schema.ts",
"schemas/enums/LabRxTemplateScalarFieldEnum.schema.ts",
"schemas/enums/CommissionBatchScalarFieldEnum.schema.ts", "schemas/enums/CommissionBatchScalarFieldEnum.schema.ts",
"schemas/enums/CommissionBatchItemScalarFieldEnum.schema.ts", "schemas/enums/CommissionBatchItemScalarFieldEnum.schema.ts",
"schemas/enums/SeleniumSettingsScalarFieldEnum.schema.ts",
"schemas/enums/SortOrder.schema.ts", "schemas/enums/SortOrder.schema.ts",
"schemas/enums/NullableJsonNullValueInput.schema.ts", "schemas/enums/NullableJsonNullValueInput.schema.ts",
"schemas/enums/JsonNullValueInput.schema.ts", "schemas/enums/JsonNullValueInput.schema.ts",
@@ -217,6 +219,11 @@
"schemas/objects/PatientConversationWhereUniqueInput.schema.ts", "schemas/objects/PatientConversationWhereUniqueInput.schema.ts",
"schemas/objects/PatientConversationOrderByWithAggregationInput.schema.ts", "schemas/objects/PatientConversationOrderByWithAggregationInput.schema.ts",
"schemas/objects/PatientConversationScalarWhereWithAggregatesInput.schema.ts", "schemas/objects/PatientConversationScalarWhereWithAggregatesInput.schema.ts",
"schemas/objects/LabRxTemplateWhereInput.schema.ts",
"schemas/objects/LabRxTemplateOrderByWithRelationInput.schema.ts",
"schemas/objects/LabRxTemplateWhereUniqueInput.schema.ts",
"schemas/objects/LabRxTemplateOrderByWithAggregationInput.schema.ts",
"schemas/objects/LabRxTemplateScalarWhereWithAggregatesInput.schema.ts",
"schemas/objects/CommissionBatchWhereInput.schema.ts", "schemas/objects/CommissionBatchWhereInput.schema.ts",
"schemas/objects/CommissionBatchOrderByWithRelationInput.schema.ts", "schemas/objects/CommissionBatchOrderByWithRelationInput.schema.ts",
"schemas/objects/CommissionBatchWhereUniqueInput.schema.ts", "schemas/objects/CommissionBatchWhereUniqueInput.schema.ts",
@@ -227,6 +234,11 @@
"schemas/objects/CommissionBatchItemWhereUniqueInput.schema.ts", "schemas/objects/CommissionBatchItemWhereUniqueInput.schema.ts",
"schemas/objects/CommissionBatchItemOrderByWithAggregationInput.schema.ts", "schemas/objects/CommissionBatchItemOrderByWithAggregationInput.schema.ts",
"schemas/objects/CommissionBatchItemScalarWhereWithAggregatesInput.schema.ts", "schemas/objects/CommissionBatchItemScalarWhereWithAggregatesInput.schema.ts",
"schemas/objects/SeleniumSettingsWhereInput.schema.ts",
"schemas/objects/SeleniumSettingsOrderByWithRelationInput.schema.ts",
"schemas/objects/SeleniumSettingsWhereUniqueInput.schema.ts",
"schemas/objects/SeleniumSettingsOrderByWithAggregationInput.schema.ts",
"schemas/objects/SeleniumSettingsScalarWhereWithAggregatesInput.schema.ts",
"schemas/objects/UserCreateInput.schema.ts", "schemas/objects/UserCreateInput.schema.ts",
"schemas/objects/UserUncheckedCreateInput.schema.ts", "schemas/objects/UserUncheckedCreateInput.schema.ts",
"schemas/objects/UserUpdateInput.schema.ts", "schemas/objects/UserUpdateInput.schema.ts",
@@ -451,6 +463,13 @@
"schemas/objects/PatientConversationCreateManyInput.schema.ts", "schemas/objects/PatientConversationCreateManyInput.schema.ts",
"schemas/objects/PatientConversationUpdateManyMutationInput.schema.ts", "schemas/objects/PatientConversationUpdateManyMutationInput.schema.ts",
"schemas/objects/PatientConversationUncheckedUpdateManyInput.schema.ts", "schemas/objects/PatientConversationUncheckedUpdateManyInput.schema.ts",
"schemas/objects/LabRxTemplateCreateInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedCreateInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedUpdateInput.schema.ts",
"schemas/objects/LabRxTemplateCreateManyInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateManyMutationInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedUpdateManyInput.schema.ts",
"schemas/objects/CommissionBatchCreateInput.schema.ts", "schemas/objects/CommissionBatchCreateInput.schema.ts",
"schemas/objects/CommissionBatchUncheckedCreateInput.schema.ts", "schemas/objects/CommissionBatchUncheckedCreateInput.schema.ts",
"schemas/objects/CommissionBatchUpdateInput.schema.ts", "schemas/objects/CommissionBatchUpdateInput.schema.ts",
@@ -465,6 +484,13 @@
"schemas/objects/CommissionBatchItemCreateManyInput.schema.ts", "schemas/objects/CommissionBatchItemCreateManyInput.schema.ts",
"schemas/objects/CommissionBatchItemUpdateManyMutationInput.schema.ts", "schemas/objects/CommissionBatchItemUpdateManyMutationInput.schema.ts",
"schemas/objects/CommissionBatchItemUncheckedUpdateManyInput.schema.ts", "schemas/objects/CommissionBatchItemUncheckedUpdateManyInput.schema.ts",
"schemas/objects/SeleniumSettingsCreateInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedCreateInput.schema.ts",
"schemas/objects/SeleniumSettingsUpdateInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedUpdateInput.schema.ts",
"schemas/objects/SeleniumSettingsCreateManyInput.schema.ts",
"schemas/objects/SeleniumSettingsUpdateManyMutationInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedUpdateManyInput.schema.ts",
"schemas/objects/IntFilter.schema.ts", "schemas/objects/IntFilter.schema.ts",
"schemas/objects/StringFilter.schema.ts", "schemas/objects/StringFilter.schema.ts",
"schemas/objects/BoolFilter.schema.ts", "schemas/objects/BoolFilter.schema.ts",
@@ -489,6 +515,8 @@
"schemas/objects/ProcedureTimeslotNullableScalarRelationFilter.schema.ts", "schemas/objects/ProcedureTimeslotNullableScalarRelationFilter.schema.ts",
"schemas/objects/InsuranceContactListRelationFilter.schema.ts", "schemas/objects/InsuranceContactListRelationFilter.schema.ts",
"schemas/objects/PatientConversationListRelationFilter.schema.ts", "schemas/objects/PatientConversationListRelationFilter.schema.ts",
"schemas/objects/LabRxTemplateListRelationFilter.schema.ts",
"schemas/objects/SeleniumSettingsNullableScalarRelationFilter.schema.ts",
"schemas/objects/PatientOrderByRelationAggregateInput.schema.ts", "schemas/objects/PatientOrderByRelationAggregateInput.schema.ts",
"schemas/objects/AppointmentOrderByRelationAggregateInput.schema.ts", "schemas/objects/AppointmentOrderByRelationAggregateInput.schema.ts",
"schemas/objects/StaffOrderByRelationAggregateInput.schema.ts", "schemas/objects/StaffOrderByRelationAggregateInput.schema.ts",
@@ -505,6 +533,7 @@
"schemas/objects/CommunicationOrderByRelationAggregateInput.schema.ts", "schemas/objects/CommunicationOrderByRelationAggregateInput.schema.ts",
"schemas/objects/InsuranceContactOrderByRelationAggregateInput.schema.ts", "schemas/objects/InsuranceContactOrderByRelationAggregateInput.schema.ts",
"schemas/objects/PatientConversationOrderByRelationAggregateInput.schema.ts", "schemas/objects/PatientConversationOrderByRelationAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateOrderByRelationAggregateInput.schema.ts",
"schemas/objects/UserCountOrderByAggregateInput.schema.ts", "schemas/objects/UserCountOrderByAggregateInput.schema.ts",
"schemas/objects/UserAvgOrderByAggregateInput.schema.ts", "schemas/objects/UserAvgOrderByAggregateInput.schema.ts",
"schemas/objects/UserMaxOrderByAggregateInput.schema.ts", "schemas/objects/UserMaxOrderByAggregateInput.schema.ts",
@@ -756,6 +785,11 @@
"schemas/objects/PatientConversationMaxOrderByAggregateInput.schema.ts", "schemas/objects/PatientConversationMaxOrderByAggregateInput.schema.ts",
"schemas/objects/PatientConversationMinOrderByAggregateInput.schema.ts", "schemas/objects/PatientConversationMinOrderByAggregateInput.schema.ts",
"schemas/objects/PatientConversationSumOrderByAggregateInput.schema.ts", "schemas/objects/PatientConversationSumOrderByAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateCountOrderByAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateAvgOrderByAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateMaxOrderByAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateMinOrderByAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateSumOrderByAggregateInput.schema.ts",
"schemas/objects/NpiProviderScalarRelationFilter.schema.ts", "schemas/objects/NpiProviderScalarRelationFilter.schema.ts",
"schemas/objects/CommissionBatchCountOrderByAggregateInput.schema.ts", "schemas/objects/CommissionBatchCountOrderByAggregateInput.schema.ts",
"schemas/objects/CommissionBatchAvgOrderByAggregateInput.schema.ts", "schemas/objects/CommissionBatchAvgOrderByAggregateInput.schema.ts",
@@ -769,6 +803,11 @@
"schemas/objects/CommissionBatchItemMaxOrderByAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemMaxOrderByAggregateInput.schema.ts",
"schemas/objects/CommissionBatchItemMinOrderByAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemMinOrderByAggregateInput.schema.ts",
"schemas/objects/CommissionBatchItemSumOrderByAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemSumOrderByAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsCountOrderByAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsAvgOrderByAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsMaxOrderByAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsMinOrderByAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsSumOrderByAggregateInput.schema.ts",
"schemas/objects/PatientCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/PatientCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/AppointmentCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/AppointmentCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/StaffCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/StaffCreateNestedManyWithoutUserInput.schema.ts",
@@ -790,6 +829,8 @@
"schemas/objects/ProcedureTimeslotCreateNestedOneWithoutUserInput.schema.ts", "schemas/objects/ProcedureTimeslotCreateNestedOneWithoutUserInput.schema.ts",
"schemas/objects/InsuranceContactCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/InsuranceContactCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/PatientConversationCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsCreateNestedOneWithoutUserInput.schema.ts",
"schemas/objects/PatientUncheckedCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/PatientUncheckedCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/AppointmentUncheckedCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/AppointmentUncheckedCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/StaffUncheckedCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/StaffUncheckedCreateNestedManyWithoutUserInput.schema.ts",
@@ -811,6 +852,8 @@
"schemas/objects/ProcedureTimeslotUncheckedCreateNestedOneWithoutUserInput.schema.ts", "schemas/objects/ProcedureTimeslotUncheckedCreateNestedOneWithoutUserInput.schema.ts",
"schemas/objects/InsuranceContactUncheckedCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/InsuranceContactUncheckedCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationUncheckedCreateNestedManyWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUncheckedCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedCreateNestedManyWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedCreateNestedOneWithoutUserInput.schema.ts",
"schemas/objects/StringFieldUpdateOperationsInput.schema.ts", "schemas/objects/StringFieldUpdateOperationsInput.schema.ts",
"schemas/objects/BoolFieldUpdateOperationsInput.schema.ts", "schemas/objects/BoolFieldUpdateOperationsInput.schema.ts",
"schemas/objects/IntFieldUpdateOperationsInput.schema.ts", "schemas/objects/IntFieldUpdateOperationsInput.schema.ts",
@@ -835,6 +878,8 @@
"schemas/objects/ProcedureTimeslotUpdateOneWithoutUserNestedInput.schema.ts", "schemas/objects/ProcedureTimeslotUpdateOneWithoutUserNestedInput.schema.ts",
"schemas/objects/InsuranceContactUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/InsuranceContactUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/PatientConversationUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/PatientConversationUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/SeleniumSettingsUpdateOneWithoutUserNestedInput.schema.ts",
"schemas/objects/PatientUncheckedUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/PatientUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/AppointmentUncheckedUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/AppointmentUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/StaffUncheckedUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/StaffUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
@@ -856,6 +901,8 @@
"schemas/objects/ProcedureTimeslotUncheckedUpdateOneWithoutUserNestedInput.schema.ts", "schemas/objects/ProcedureTimeslotUncheckedUpdateOneWithoutUserNestedInput.schema.ts",
"schemas/objects/InsuranceContactUncheckedUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/InsuranceContactUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/PatientConversationUncheckedUpdateManyWithoutUserNestedInput.schema.ts", "schemas/objects/PatientConversationUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedUpdateManyWithoutUserNestedInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedUpdateOneWithoutUserNestedInput.schema.ts",
"schemas/objects/UserCreateNestedOneWithoutPatientsInput.schema.ts", "schemas/objects/UserCreateNestedOneWithoutPatientsInput.schema.ts",
"schemas/objects/AppointmentCreateNestedManyWithoutPatientInput.schema.ts", "schemas/objects/AppointmentCreateNestedManyWithoutPatientInput.schema.ts",
"schemas/objects/AppointmentProcedureCreateNestedManyWithoutPatientInput.schema.ts", "schemas/objects/AppointmentProcedureCreateNestedManyWithoutPatientInput.schema.ts",
@@ -1088,6 +1135,8 @@
"schemas/objects/UserCreateNestedOneWithoutPatientConversationsInput.schema.ts", "schemas/objects/UserCreateNestedOneWithoutPatientConversationsInput.schema.ts",
"schemas/objects/PatientUpdateOneRequiredWithoutConversationNestedInput.schema.ts", "schemas/objects/PatientUpdateOneRequiredWithoutConversationNestedInput.schema.ts",
"schemas/objects/UserUpdateOneRequiredWithoutPatientConversationsNestedInput.schema.ts", "schemas/objects/UserUpdateOneRequiredWithoutPatientConversationsNestedInput.schema.ts",
"schemas/objects/UserCreateNestedOneWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUpdateOneRequiredWithoutLabRxTemplatesNestedInput.schema.ts",
"schemas/objects/NpiProviderCreateNestedOneWithoutCommissionBatchesInput.schema.ts", "schemas/objects/NpiProviderCreateNestedOneWithoutCommissionBatchesInput.schema.ts",
"schemas/objects/CommissionBatchItemCreateNestedManyWithoutCommissionBatchInput.schema.ts", "schemas/objects/CommissionBatchItemCreateNestedManyWithoutCommissionBatchInput.schema.ts",
"schemas/objects/CommissionBatchItemUncheckedCreateNestedManyWithoutCommissionBatchInput.schema.ts", "schemas/objects/CommissionBatchItemUncheckedCreateNestedManyWithoutCommissionBatchInput.schema.ts",
@@ -1098,6 +1147,8 @@
"schemas/objects/PaymentCreateNestedOneWithoutCommissionBatchItemsInput.schema.ts", "schemas/objects/PaymentCreateNestedOneWithoutCommissionBatchItemsInput.schema.ts",
"schemas/objects/CommissionBatchUpdateOneRequiredWithoutItemsNestedInput.schema.ts", "schemas/objects/CommissionBatchUpdateOneRequiredWithoutItemsNestedInput.schema.ts",
"schemas/objects/PaymentUpdateOneRequiredWithoutCommissionBatchItemsNestedInput.schema.ts", "schemas/objects/PaymentUpdateOneRequiredWithoutCommissionBatchItemsNestedInput.schema.ts",
"schemas/objects/UserCreateNestedOneWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUpdateOneRequiredWithoutSeleniumSettingsNestedInput.schema.ts",
"schemas/objects/NestedIntFilter.schema.ts", "schemas/objects/NestedIntFilter.schema.ts",
"schemas/objects/NestedStringFilter.schema.ts", "schemas/objects/NestedStringFilter.schema.ts",
"schemas/objects/NestedBoolFilter.schema.ts", "schemas/objects/NestedBoolFilter.schema.ts",
@@ -1227,6 +1278,13 @@
"schemas/objects/PatientConversationUncheckedCreateWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUncheckedCreateWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationCreateOrConnectWithoutUserInput.schema.ts", "schemas/objects/PatientConversationCreateOrConnectWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationCreateManyUserInputEnvelope.schema.ts", "schemas/objects/PatientConversationCreateManyUserInputEnvelope.schema.ts",
"schemas/objects/LabRxTemplateCreateWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedCreateWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateCreateOrConnectWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateCreateManyUserInputEnvelope.schema.ts",
"schemas/objects/SeleniumSettingsCreateWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedCreateWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsCreateOrConnectWithoutUserInput.schema.ts",
"schemas/objects/PatientUpsertWithWhereUniqueWithoutUserInput.schema.ts", "schemas/objects/PatientUpsertWithWhereUniqueWithoutUserInput.schema.ts",
"schemas/objects/PatientUpdateWithWhereUniqueWithoutUserInput.schema.ts", "schemas/objects/PatientUpdateWithWhereUniqueWithoutUserInput.schema.ts",
"schemas/objects/PatientUpdateManyWithWhereWithoutUserInput.schema.ts", "schemas/objects/PatientUpdateManyWithWhereWithoutUserInput.schema.ts",
@@ -1311,6 +1369,14 @@
"schemas/objects/PatientConversationUpdateWithWhereUniqueWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUpdateWithWhereUniqueWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationUpdateManyWithWhereWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUpdateManyWithWhereWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationScalarWhereInput.schema.ts", "schemas/objects/PatientConversationScalarWhereInput.schema.ts",
"schemas/objects/LabRxTemplateUpsertWithWhereUniqueWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateWithWhereUniqueWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateManyWithWhereWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateScalarWhereInput.schema.ts",
"schemas/objects/SeleniumSettingsUpsertWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsUpdateToOneWithWhereWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsUpdateWithoutUserInput.schema.ts",
"schemas/objects/SeleniumSettingsUncheckedUpdateWithoutUserInput.schema.ts",
"schemas/objects/UserCreateWithoutPatientsInput.schema.ts", "schemas/objects/UserCreateWithoutPatientsInput.schema.ts",
"schemas/objects/UserUncheckedCreateWithoutPatientsInput.schema.ts", "schemas/objects/UserUncheckedCreateWithoutPatientsInput.schema.ts",
"schemas/objects/UserCreateOrConnectWithoutPatientsInput.schema.ts", "schemas/objects/UserCreateOrConnectWithoutPatientsInput.schema.ts",
@@ -1861,6 +1927,13 @@
"schemas/objects/UserUpdateToOneWithWhereWithoutPatientConversationsInput.schema.ts", "schemas/objects/UserUpdateToOneWithWhereWithoutPatientConversationsInput.schema.ts",
"schemas/objects/UserUpdateWithoutPatientConversationsInput.schema.ts", "schemas/objects/UserUpdateWithoutPatientConversationsInput.schema.ts",
"schemas/objects/UserUncheckedUpdateWithoutPatientConversationsInput.schema.ts", "schemas/objects/UserUncheckedUpdateWithoutPatientConversationsInput.schema.ts",
"schemas/objects/UserCreateWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUncheckedCreateWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserCreateOrConnectWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUpsertWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUpdateToOneWithWhereWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUpdateWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/UserUncheckedUpdateWithoutLabRxTemplatesInput.schema.ts",
"schemas/objects/NpiProviderCreateWithoutCommissionBatchesInput.schema.ts", "schemas/objects/NpiProviderCreateWithoutCommissionBatchesInput.schema.ts",
"schemas/objects/NpiProviderUncheckedCreateWithoutCommissionBatchesInput.schema.ts", "schemas/objects/NpiProviderUncheckedCreateWithoutCommissionBatchesInput.schema.ts",
"schemas/objects/NpiProviderCreateOrConnectWithoutCommissionBatchesInput.schema.ts", "schemas/objects/NpiProviderCreateOrConnectWithoutCommissionBatchesInput.schema.ts",
@@ -1889,6 +1962,13 @@
"schemas/objects/PaymentUpdateToOneWithWhereWithoutCommissionBatchItemsInput.schema.ts", "schemas/objects/PaymentUpdateToOneWithWhereWithoutCommissionBatchItemsInput.schema.ts",
"schemas/objects/PaymentUpdateWithoutCommissionBatchItemsInput.schema.ts", "schemas/objects/PaymentUpdateWithoutCommissionBatchItemsInput.schema.ts",
"schemas/objects/PaymentUncheckedUpdateWithoutCommissionBatchItemsInput.schema.ts", "schemas/objects/PaymentUncheckedUpdateWithoutCommissionBatchItemsInput.schema.ts",
"schemas/objects/UserCreateWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUncheckedCreateWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserCreateOrConnectWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUpsertWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUpdateToOneWithWhereWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUpdateWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/UserUncheckedUpdateWithoutSeleniumSettingsInput.schema.ts",
"schemas/objects/PatientCreateManyUserInput.schema.ts", "schemas/objects/PatientCreateManyUserInput.schema.ts",
"schemas/objects/AppointmentCreateManyUserInput.schema.ts", "schemas/objects/AppointmentCreateManyUserInput.schema.ts",
"schemas/objects/StaffCreateManyUserInput.schema.ts", "schemas/objects/StaffCreateManyUserInput.schema.ts",
@@ -1905,6 +1985,7 @@
"schemas/objects/CommunicationCreateManyUserInput.schema.ts", "schemas/objects/CommunicationCreateManyUserInput.schema.ts",
"schemas/objects/InsuranceContactCreateManyUserInput.schema.ts", "schemas/objects/InsuranceContactCreateManyUserInput.schema.ts",
"schemas/objects/PatientConversationCreateManyUserInput.schema.ts", "schemas/objects/PatientConversationCreateManyUserInput.schema.ts",
"schemas/objects/LabRxTemplateCreateManyUserInput.schema.ts",
"schemas/objects/PatientUpdateWithoutUserInput.schema.ts", "schemas/objects/PatientUpdateWithoutUserInput.schema.ts",
"schemas/objects/PatientUncheckedUpdateWithoutUserInput.schema.ts", "schemas/objects/PatientUncheckedUpdateWithoutUserInput.schema.ts",
"schemas/objects/PatientUncheckedUpdateManyWithoutUserInput.schema.ts", "schemas/objects/PatientUncheckedUpdateManyWithoutUserInput.schema.ts",
@@ -1953,6 +2034,9 @@
"schemas/objects/PatientConversationUpdateWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUpdateWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationUncheckedUpdateWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUncheckedUpdateWithoutUserInput.schema.ts",
"schemas/objects/PatientConversationUncheckedUpdateManyWithoutUserInput.schema.ts", "schemas/objects/PatientConversationUncheckedUpdateManyWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUpdateWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedUpdateWithoutUserInput.schema.ts",
"schemas/objects/LabRxTemplateUncheckedUpdateManyWithoutUserInput.schema.ts",
"schemas/objects/AppointmentCreateManyPatientInput.schema.ts", "schemas/objects/AppointmentCreateManyPatientInput.schema.ts",
"schemas/objects/AppointmentProcedureCreateManyPatientInput.schema.ts", "schemas/objects/AppointmentProcedureCreateManyPatientInput.schema.ts",
"schemas/objects/ClaimCreateManyPatientInput.schema.ts", "schemas/objects/ClaimCreateManyPatientInput.schema.ts",
@@ -2225,6 +2309,11 @@
"schemas/objects/PatientConversationSumAggregateInput.schema.ts", "schemas/objects/PatientConversationSumAggregateInput.schema.ts",
"schemas/objects/PatientConversationMinAggregateInput.schema.ts", "schemas/objects/PatientConversationMinAggregateInput.schema.ts",
"schemas/objects/PatientConversationMaxAggregateInput.schema.ts", "schemas/objects/PatientConversationMaxAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateCountAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateAvgAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateSumAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateMinAggregateInput.schema.ts",
"schemas/objects/LabRxTemplateMaxAggregateInput.schema.ts",
"schemas/objects/CommissionBatchCountAggregateInput.schema.ts", "schemas/objects/CommissionBatchCountAggregateInput.schema.ts",
"schemas/objects/CommissionBatchAvgAggregateInput.schema.ts", "schemas/objects/CommissionBatchAvgAggregateInput.schema.ts",
"schemas/objects/CommissionBatchSumAggregateInput.schema.ts", "schemas/objects/CommissionBatchSumAggregateInput.schema.ts",
@@ -2235,6 +2324,11 @@
"schemas/objects/CommissionBatchItemSumAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemSumAggregateInput.schema.ts",
"schemas/objects/CommissionBatchItemMinAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemMinAggregateInput.schema.ts",
"schemas/objects/CommissionBatchItemMaxAggregateInput.schema.ts", "schemas/objects/CommissionBatchItemMaxAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsCountAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsAvgAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsSumAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsMinAggregateInput.schema.ts",
"schemas/objects/SeleniumSettingsMaxAggregateInput.schema.ts",
"schemas/objects/UserCountOutputTypeSelect.schema.ts", "schemas/objects/UserCountOutputTypeSelect.schema.ts",
"schemas/objects/PatientCountOutputTypeSelect.schema.ts", "schemas/objects/PatientCountOutputTypeSelect.schema.ts",
"schemas/objects/AppointmentCountOutputTypeSelect.schema.ts", "schemas/objects/AppointmentCountOutputTypeSelect.schema.ts",
@@ -2264,6 +2358,7 @@
"schemas/objects/UserCountOutputTypeCountCommunicationsArgs.schema.ts", "schemas/objects/UserCountOutputTypeCountCommunicationsArgs.schema.ts",
"schemas/objects/UserCountOutputTypeCountInsuranceContactsArgs.schema.ts", "schemas/objects/UserCountOutputTypeCountInsuranceContactsArgs.schema.ts",
"schemas/objects/UserCountOutputTypeCountPatientConversationsArgs.schema.ts", "schemas/objects/UserCountOutputTypeCountPatientConversationsArgs.schema.ts",
"schemas/objects/UserCountOutputTypeCountLabRxTemplatesArgs.schema.ts",
"schemas/objects/PatientCountOutputTypeArgs.schema.ts", "schemas/objects/PatientCountOutputTypeArgs.schema.ts",
"schemas/objects/PatientCountOutputTypeCountAppointmentsArgs.schema.ts", "schemas/objects/PatientCountOutputTypeCountAppointmentsArgs.schema.ts",
"schemas/objects/PatientCountOutputTypeCountProceduresArgs.schema.ts", "schemas/objects/PatientCountOutputTypeCountProceduresArgs.schema.ts",
@@ -2335,8 +2430,10 @@
"schemas/objects/InsuranceContactSelect.schema.ts", "schemas/objects/InsuranceContactSelect.schema.ts",
"schemas/objects/ProcedureTimeslotSelect.schema.ts", "schemas/objects/ProcedureTimeslotSelect.schema.ts",
"schemas/objects/PatientConversationSelect.schema.ts", "schemas/objects/PatientConversationSelect.schema.ts",
"schemas/objects/LabRxTemplateSelect.schema.ts",
"schemas/objects/CommissionBatchSelect.schema.ts", "schemas/objects/CommissionBatchSelect.schema.ts",
"schemas/objects/CommissionBatchItemSelect.schema.ts", "schemas/objects/CommissionBatchItemSelect.schema.ts",
"schemas/objects/SeleniumSettingsSelect.schema.ts",
"schemas/objects/UserArgs.schema.ts", "schemas/objects/UserArgs.schema.ts",
"schemas/objects/PatientArgs.schema.ts", "schemas/objects/PatientArgs.schema.ts",
"schemas/objects/AppointmentArgs.schema.ts", "schemas/objects/AppointmentArgs.schema.ts",
@@ -2369,8 +2466,10 @@
"schemas/objects/InsuranceContactArgs.schema.ts", "schemas/objects/InsuranceContactArgs.schema.ts",
"schemas/objects/ProcedureTimeslotArgs.schema.ts", "schemas/objects/ProcedureTimeslotArgs.schema.ts",
"schemas/objects/PatientConversationArgs.schema.ts", "schemas/objects/PatientConversationArgs.schema.ts",
"schemas/objects/LabRxTemplateArgs.schema.ts",
"schemas/objects/CommissionBatchArgs.schema.ts", "schemas/objects/CommissionBatchArgs.schema.ts",
"schemas/objects/CommissionBatchItemArgs.schema.ts", "schemas/objects/CommissionBatchItemArgs.schema.ts",
"schemas/objects/SeleniumSettingsArgs.schema.ts",
"schemas/objects/UserInclude.schema.ts", "schemas/objects/UserInclude.schema.ts",
"schemas/objects/PatientInclude.schema.ts", "schemas/objects/PatientInclude.schema.ts",
"schemas/objects/AppointmentInclude.schema.ts", "schemas/objects/AppointmentInclude.schema.ts",
@@ -2402,8 +2501,10 @@
"schemas/objects/InsuranceContactInclude.schema.ts", "schemas/objects/InsuranceContactInclude.schema.ts",
"schemas/objects/ProcedureTimeslotInclude.schema.ts", "schemas/objects/ProcedureTimeslotInclude.schema.ts",
"schemas/objects/PatientConversationInclude.schema.ts", "schemas/objects/PatientConversationInclude.schema.ts",
"schemas/objects/LabRxTemplateInclude.schema.ts",
"schemas/objects/CommissionBatchInclude.schema.ts", "schemas/objects/CommissionBatchInclude.schema.ts",
"schemas/objects/CommissionBatchItemInclude.schema.ts", "schemas/objects/CommissionBatchItemInclude.schema.ts",
"schemas/objects/SeleniumSettingsInclude.schema.ts",
"schemas/findUniqueUser.schema.ts", "schemas/findUniqueUser.schema.ts",
"schemas/findUniqueOrThrowUser.schema.ts", "schemas/findUniqueOrThrowUser.schema.ts",
"schemas/findFirstUser.schema.ts", "schemas/findFirstUser.schema.ts",
@@ -2948,6 +3049,23 @@
"schemas/upsertOnePatientConversation.schema.ts", "schemas/upsertOnePatientConversation.schema.ts",
"schemas/aggregatePatientConversation.schema.ts", "schemas/aggregatePatientConversation.schema.ts",
"schemas/groupByPatientConversation.schema.ts", "schemas/groupByPatientConversation.schema.ts",
"schemas/findUniqueLabRxTemplate.schema.ts",
"schemas/findUniqueOrThrowLabRxTemplate.schema.ts",
"schemas/findFirstLabRxTemplate.schema.ts",
"schemas/findFirstOrThrowLabRxTemplate.schema.ts",
"schemas/findManyLabRxTemplate.schema.ts",
"schemas/countLabRxTemplate.schema.ts",
"schemas/createOneLabRxTemplate.schema.ts",
"schemas/createManyLabRxTemplate.schema.ts",
"schemas/createManyAndReturnLabRxTemplate.schema.ts",
"schemas/deleteOneLabRxTemplate.schema.ts",
"schemas/deleteManyLabRxTemplate.schema.ts",
"schemas/updateOneLabRxTemplate.schema.ts",
"schemas/updateManyLabRxTemplate.schema.ts",
"schemas/updateManyAndReturnLabRxTemplate.schema.ts",
"schemas/upsertOneLabRxTemplate.schema.ts",
"schemas/aggregateLabRxTemplate.schema.ts",
"schemas/groupByLabRxTemplate.schema.ts",
"schemas/findUniqueCommissionBatch.schema.ts", "schemas/findUniqueCommissionBatch.schema.ts",
"schemas/findUniqueOrThrowCommissionBatch.schema.ts", "schemas/findUniqueOrThrowCommissionBatch.schema.ts",
"schemas/findFirstCommissionBatch.schema.ts", "schemas/findFirstCommissionBatch.schema.ts",
@@ -2982,6 +3100,23 @@
"schemas/upsertOneCommissionBatchItem.schema.ts", "schemas/upsertOneCommissionBatchItem.schema.ts",
"schemas/aggregateCommissionBatchItem.schema.ts", "schemas/aggregateCommissionBatchItem.schema.ts",
"schemas/groupByCommissionBatchItem.schema.ts", "schemas/groupByCommissionBatchItem.schema.ts",
"schemas/findUniqueSeleniumSettings.schema.ts",
"schemas/findUniqueOrThrowSeleniumSettings.schema.ts",
"schemas/findFirstSeleniumSettings.schema.ts",
"schemas/findFirstOrThrowSeleniumSettings.schema.ts",
"schemas/findManySeleniumSettings.schema.ts",
"schemas/countSeleniumSettings.schema.ts",
"schemas/createOneSeleniumSettings.schema.ts",
"schemas/createManySeleniumSettings.schema.ts",
"schemas/createManyAndReturnSeleniumSettings.schema.ts",
"schemas/deleteOneSeleniumSettings.schema.ts",
"schemas/deleteManySeleniumSettings.schema.ts",
"schemas/updateOneSeleniumSettings.schema.ts",
"schemas/updateManySeleniumSettings.schema.ts",
"schemas/updateManyAndReturnSeleniumSettings.schema.ts",
"schemas/upsertOneSeleniumSettings.schema.ts",
"schemas/aggregateSeleniumSettings.schema.ts",
"schemas/groupBySeleniumSettings.schema.ts",
"schemas/results/UserFindUniqueResult.schema.ts", "schemas/results/UserFindUniqueResult.schema.ts",
"schemas/results/UserFindFirstResult.schema.ts", "schemas/results/UserFindFirstResult.schema.ts",
"schemas/results/UserFindManyResult.schema.ts", "schemas/results/UserFindManyResult.schema.ts",
@@ -3398,6 +3533,19 @@
"schemas/results/PatientConversationAggregateResult.schema.ts", "schemas/results/PatientConversationAggregateResult.schema.ts",
"schemas/results/PatientConversationGroupByResult.schema.ts", "schemas/results/PatientConversationGroupByResult.schema.ts",
"schemas/results/PatientConversationCountResult.schema.ts", "schemas/results/PatientConversationCountResult.schema.ts",
"schemas/results/LabRxTemplateFindUniqueResult.schema.ts",
"schemas/results/LabRxTemplateFindFirstResult.schema.ts",
"schemas/results/LabRxTemplateFindManyResult.schema.ts",
"schemas/results/LabRxTemplateCreateResult.schema.ts",
"schemas/results/LabRxTemplateCreateManyResult.schema.ts",
"schemas/results/LabRxTemplateUpdateResult.schema.ts",
"schemas/results/LabRxTemplateUpdateManyResult.schema.ts",
"schemas/results/LabRxTemplateUpsertResult.schema.ts",
"schemas/results/LabRxTemplateDeleteResult.schema.ts",
"schemas/results/LabRxTemplateDeleteManyResult.schema.ts",
"schemas/results/LabRxTemplateAggregateResult.schema.ts",
"schemas/results/LabRxTemplateGroupByResult.schema.ts",
"schemas/results/LabRxTemplateCountResult.schema.ts",
"schemas/results/CommissionBatchFindUniqueResult.schema.ts", "schemas/results/CommissionBatchFindUniqueResult.schema.ts",
"schemas/results/CommissionBatchFindFirstResult.schema.ts", "schemas/results/CommissionBatchFindFirstResult.schema.ts",
"schemas/results/CommissionBatchFindManyResult.schema.ts", "schemas/results/CommissionBatchFindManyResult.schema.ts",
@@ -3424,6 +3572,19 @@
"schemas/results/CommissionBatchItemAggregateResult.schema.ts", "schemas/results/CommissionBatchItemAggregateResult.schema.ts",
"schemas/results/CommissionBatchItemGroupByResult.schema.ts", "schemas/results/CommissionBatchItemGroupByResult.schema.ts",
"schemas/results/CommissionBatchItemCountResult.schema.ts", "schemas/results/CommissionBatchItemCountResult.schema.ts",
"schemas/results/SeleniumSettingsFindUniqueResult.schema.ts",
"schemas/results/SeleniumSettingsFindFirstResult.schema.ts",
"schemas/results/SeleniumSettingsFindManyResult.schema.ts",
"schemas/results/SeleniumSettingsCreateResult.schema.ts",
"schemas/results/SeleniumSettingsCreateManyResult.schema.ts",
"schemas/results/SeleniumSettingsUpdateResult.schema.ts",
"schemas/results/SeleniumSettingsUpdateManyResult.schema.ts",
"schemas/results/SeleniumSettingsUpsertResult.schema.ts",
"schemas/results/SeleniumSettingsDeleteResult.schema.ts",
"schemas/results/SeleniumSettingsDeleteManyResult.schema.ts",
"schemas/results/SeleniumSettingsAggregateResult.schema.ts",
"schemas/results/SeleniumSettingsGroupByResult.schema.ts",
"schemas/results/SeleniumSettingsCountResult.schema.ts",
"schemas/results/index.ts", "schemas/results/index.ts",
"schemas/index.ts", "schemas/index.ts",
"schemas/variants/pure/User.pure.ts", "schemas/variants/pure/User.pure.ts",
@@ -3458,8 +3619,10 @@
"schemas/variants/pure/InsuranceContact.pure.ts", "schemas/variants/pure/InsuranceContact.pure.ts",
"schemas/variants/pure/ProcedureTimeslot.pure.ts", "schemas/variants/pure/ProcedureTimeslot.pure.ts",
"schemas/variants/pure/PatientConversation.pure.ts", "schemas/variants/pure/PatientConversation.pure.ts",
"schemas/variants/pure/LabRxTemplate.pure.ts",
"schemas/variants/pure/CommissionBatch.pure.ts", "schemas/variants/pure/CommissionBatch.pure.ts",
"schemas/variants/pure/CommissionBatchItem.pure.ts", "schemas/variants/pure/CommissionBatchItem.pure.ts",
"schemas/variants/pure/SeleniumSettings.pure.ts",
"schemas/variants/pure/index.ts", "schemas/variants/pure/index.ts",
"schemas/variants/input/User.input.ts", "schemas/variants/input/User.input.ts",
"schemas/variants/input/Patient.input.ts", "schemas/variants/input/Patient.input.ts",
@@ -3493,8 +3656,10 @@
"schemas/variants/input/InsuranceContact.input.ts", "schemas/variants/input/InsuranceContact.input.ts",
"schemas/variants/input/ProcedureTimeslot.input.ts", "schemas/variants/input/ProcedureTimeslot.input.ts",
"schemas/variants/input/PatientConversation.input.ts", "schemas/variants/input/PatientConversation.input.ts",
"schemas/variants/input/LabRxTemplate.input.ts",
"schemas/variants/input/CommissionBatch.input.ts", "schemas/variants/input/CommissionBatch.input.ts",
"schemas/variants/input/CommissionBatchItem.input.ts", "schemas/variants/input/CommissionBatchItem.input.ts",
"schemas/variants/input/SeleniumSettings.input.ts",
"schemas/variants/input/index.ts", "schemas/variants/input/index.ts",
"schemas/variants/result/User.result.ts", "schemas/variants/result/User.result.ts",
"schemas/variants/result/Patient.result.ts", "schemas/variants/result/Patient.result.ts",
@@ -3528,8 +3693,10 @@
"schemas/variants/result/InsuranceContact.result.ts", "schemas/variants/result/InsuranceContact.result.ts",
"schemas/variants/result/ProcedureTimeslot.result.ts", "schemas/variants/result/ProcedureTimeslot.result.ts",
"schemas/variants/result/PatientConversation.result.ts", "schemas/variants/result/PatientConversation.result.ts",
"schemas/variants/result/LabRxTemplate.result.ts",
"schemas/variants/result/CommissionBatch.result.ts", "schemas/variants/result/CommissionBatch.result.ts",
"schemas/variants/result/CommissionBatchItem.result.ts", "schemas/variants/result/CommissionBatchItem.result.ts",
"schemas/variants/result/SeleniumSettings.result.ts",
"schemas/variants/result/index.ts", "schemas/variants/result/index.ts",
"schemas/variants/index.ts" "schemas/variants/index.ts"
], ],
@@ -1,7 +1,8 @@
import * as z from 'zod'; import * as z from 'zod';
import { Prisma } from '../../generated/prisma'; import type { Prisma } from '../../generated/prisma';
import Decimal from 'decimal.js';
import Decimal from "decimal.js";
// DECIMAL HELPERS // DECIMAL HELPERS
//------------------------------------------------------ //------------------------------------------------------
@@ -23,7 +24,7 @@ export const isValidDecimalInput = (
if (v === undefined || v === null) return false; if (v === undefined || v === null) return false;
return ( return (
// Explicit instance checks first // Explicit instance checks first
v instanceof Prisma.Decimal || v instanceof Decimal ||
// If Decimal.js is present and imported by the generator, this symbol exists at runtime // If Decimal.js is present and imported by the generator, this symbol exists at runtime
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - Decimal may be undefined when not installed; codegen controls the import // @ts-ignore - Decimal may be undefined when not installed; codegen controls the import
@@ -0,0 +1,14 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateOrderByWithRelationInputObjectSchema as LabRxTemplateOrderByWithRelationInputObjectSchema } from './objects/LabRxTemplateOrderByWithRelationInput.schema';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
import { LabRxTemplateCountAggregateInputObjectSchema as LabRxTemplateCountAggregateInputObjectSchema } from './objects/LabRxTemplateCountAggregateInput.schema';
import { LabRxTemplateMinAggregateInputObjectSchema as LabRxTemplateMinAggregateInputObjectSchema } from './objects/LabRxTemplateMinAggregateInput.schema';
import { LabRxTemplateMaxAggregateInputObjectSchema as LabRxTemplateMaxAggregateInputObjectSchema } from './objects/LabRxTemplateMaxAggregateInput.schema';
import { LabRxTemplateAvgAggregateInputObjectSchema as LabRxTemplateAvgAggregateInputObjectSchema } from './objects/LabRxTemplateAvgAggregateInput.schema';
import { LabRxTemplateSumAggregateInputObjectSchema as LabRxTemplateSumAggregateInputObjectSchema } from './objects/LabRxTemplateSumAggregateInput.schema';
export const LabRxTemplateAggregateSchema: z.ZodType<Prisma.LabRxTemplateAggregateArgs> = z.object({ orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), _count: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional(), _min: LabRxTemplateMinAggregateInputObjectSchema.optional(), _max: LabRxTemplateMaxAggregateInputObjectSchema.optional(), _avg: LabRxTemplateAvgAggregateInputObjectSchema.optional(), _sum: LabRxTemplateSumAggregateInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateAggregateArgs>;
export const LabRxTemplateAggregateZodSchema = z.object({ orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), _count: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional(), _min: LabRxTemplateMinAggregateInputObjectSchema.optional(), _max: LabRxTemplateMaxAggregateInputObjectSchema.optional(), _avg: LabRxTemplateAvgAggregateInputObjectSchema.optional(), _sum: LabRxTemplateSumAggregateInputObjectSchema.optional() }).strict();
@@ -0,0 +1,14 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsOrderByWithRelationInputObjectSchema as SeleniumSettingsOrderByWithRelationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithRelationInput.schema';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
import { SeleniumSettingsCountAggregateInputObjectSchema as SeleniumSettingsCountAggregateInputObjectSchema } from './objects/SeleniumSettingsCountAggregateInput.schema';
import { SeleniumSettingsMinAggregateInputObjectSchema as SeleniumSettingsMinAggregateInputObjectSchema } from './objects/SeleniumSettingsMinAggregateInput.schema';
import { SeleniumSettingsMaxAggregateInputObjectSchema as SeleniumSettingsMaxAggregateInputObjectSchema } from './objects/SeleniumSettingsMaxAggregateInput.schema';
import { SeleniumSettingsAvgAggregateInputObjectSchema as SeleniumSettingsAvgAggregateInputObjectSchema } from './objects/SeleniumSettingsAvgAggregateInput.schema';
import { SeleniumSettingsSumAggregateInputObjectSchema as SeleniumSettingsSumAggregateInputObjectSchema } from './objects/SeleniumSettingsSumAggregateInput.schema';
export const SeleniumSettingsAggregateSchema: z.ZodType<Prisma.SeleniumSettingsAggregateArgs> = z.object({ orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), _count: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional(), _min: SeleniumSettingsMinAggregateInputObjectSchema.optional(), _max: SeleniumSettingsMaxAggregateInputObjectSchema.optional(), _avg: SeleniumSettingsAvgAggregateInputObjectSchema.optional(), _sum: SeleniumSettingsSumAggregateInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsAggregateArgs>;
export const SeleniumSettingsAggregateZodSchema = z.object({ orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), _count: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional(), _min: SeleniumSettingsMinAggregateInputObjectSchema.optional(), _max: SeleniumSettingsMaxAggregateInputObjectSchema.optional(), _avg: SeleniumSettingsAvgAggregateInputObjectSchema.optional(), _sum: SeleniumSettingsSumAggregateInputObjectSchema.optional() }).strict();
@@ -0,0 +1,10 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateOrderByWithRelationInputObjectSchema as LabRxTemplateOrderByWithRelationInputObjectSchema } from './objects/LabRxTemplateOrderByWithRelationInput.schema';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
import { LabRxTemplateCountAggregateInputObjectSchema as LabRxTemplateCountAggregateInputObjectSchema } from './objects/LabRxTemplateCountAggregateInput.schema';
export const LabRxTemplateCountSchema: z.ZodType<Prisma.LabRxTemplateCountArgs> = z.object({ orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), select: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateCountArgs>;
export const LabRxTemplateCountZodSchema = z.object({ orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), select: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional() }).strict();
@@ -0,0 +1,10 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsOrderByWithRelationInputObjectSchema as SeleniumSettingsOrderByWithRelationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithRelationInput.schema';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
import { SeleniumSettingsCountAggregateInputObjectSchema as SeleniumSettingsCountAggregateInputObjectSchema } from './objects/SeleniumSettingsCountAggregateInput.schema';
export const SeleniumSettingsCountSchema: z.ZodType<Prisma.SeleniumSettingsCountArgs> = z.object({ orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), select: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsCountArgs>;
export const SeleniumSettingsCountZodSchema = z.object({ orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), select: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional() }).strict();
@@ -0,0 +1,8 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateSelectObjectSchema as LabRxTemplateSelectObjectSchema } from './objects/LabRxTemplateSelect.schema';
import { LabRxTemplateCreateManyInputObjectSchema as LabRxTemplateCreateManyInputObjectSchema } from './objects/LabRxTemplateCreateManyInput.schema';
export const LabRxTemplateCreateManyAndReturnSchema: z.ZodType<Prisma.LabRxTemplateCreateManyAndReturnArgs> = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), data: z.union([ LabRxTemplateCreateManyInputObjectSchema, z.array(LabRxTemplateCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateCreateManyAndReturnArgs>;
export const LabRxTemplateCreateManyAndReturnZodSchema = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), data: z.union([ LabRxTemplateCreateManyInputObjectSchema, z.array(LabRxTemplateCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict();
@@ -0,0 +1,8 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsSelectObjectSchema as SeleniumSettingsSelectObjectSchema } from './objects/SeleniumSettingsSelect.schema';
import { SeleniumSettingsCreateManyInputObjectSchema as SeleniumSettingsCreateManyInputObjectSchema } from './objects/SeleniumSettingsCreateManyInput.schema';
export const SeleniumSettingsCreateManyAndReturnSchema: z.ZodType<Prisma.SeleniumSettingsCreateManyAndReturnArgs> = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), data: z.union([ SeleniumSettingsCreateManyInputObjectSchema, z.array(SeleniumSettingsCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsCreateManyAndReturnArgs>;
export const SeleniumSettingsCreateManyAndReturnZodSchema = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), data: z.union([ SeleniumSettingsCreateManyInputObjectSchema, z.array(SeleniumSettingsCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict();
@@ -0,0 +1,7 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateCreateManyInputObjectSchema as LabRxTemplateCreateManyInputObjectSchema } from './objects/LabRxTemplateCreateManyInput.schema';
export const LabRxTemplateCreateManySchema: z.ZodType<Prisma.LabRxTemplateCreateManyArgs> = z.object({ data: z.union([ LabRxTemplateCreateManyInputObjectSchema, z.array(LabRxTemplateCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateCreateManyArgs>;
export const LabRxTemplateCreateManyZodSchema = z.object({ data: z.union([ LabRxTemplateCreateManyInputObjectSchema, z.array(LabRxTemplateCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict();
@@ -0,0 +1,7 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsCreateManyInputObjectSchema as SeleniumSettingsCreateManyInputObjectSchema } from './objects/SeleniumSettingsCreateManyInput.schema';
export const SeleniumSettingsCreateManySchema: z.ZodType<Prisma.SeleniumSettingsCreateManyArgs> = z.object({ data: z.union([ SeleniumSettingsCreateManyInputObjectSchema, z.array(SeleniumSettingsCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsCreateManyArgs>;
export const SeleniumSettingsCreateManyZodSchema = z.object({ data: z.union([ SeleniumSettingsCreateManyInputObjectSchema, z.array(SeleniumSettingsCreateManyInputObjectSchema) ]), skipDuplicates: z.boolean().optional() }).strict();
@@ -0,0 +1,10 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateSelectObjectSchema as LabRxTemplateSelectObjectSchema } from './objects/LabRxTemplateSelect.schema';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateCreateInputObjectSchema as LabRxTemplateCreateInputObjectSchema } from './objects/LabRxTemplateCreateInput.schema';
import { LabRxTemplateUncheckedCreateInputObjectSchema as LabRxTemplateUncheckedCreateInputObjectSchema } from './objects/LabRxTemplateUncheckedCreateInput.schema';
export const LabRxTemplateCreateOneSchema: z.ZodType<Prisma.LabRxTemplateCreateArgs> = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), data: z.union([LabRxTemplateCreateInputObjectSchema, LabRxTemplateUncheckedCreateInputObjectSchema]) }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateCreateArgs>;
export const LabRxTemplateCreateOneZodSchema = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), data: z.union([LabRxTemplateCreateInputObjectSchema, LabRxTemplateUncheckedCreateInputObjectSchema]) }).strict();
@@ -0,0 +1,10 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsSelectObjectSchema as SeleniumSettingsSelectObjectSchema } from './objects/SeleniumSettingsSelect.schema';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsCreateInputObjectSchema as SeleniumSettingsCreateInputObjectSchema } from './objects/SeleniumSettingsCreateInput.schema';
import { SeleniumSettingsUncheckedCreateInputObjectSchema as SeleniumSettingsUncheckedCreateInputObjectSchema } from './objects/SeleniumSettingsUncheckedCreateInput.schema';
export const SeleniumSettingsCreateOneSchema: z.ZodType<Prisma.SeleniumSettingsCreateArgs> = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), data: z.union([SeleniumSettingsCreateInputObjectSchema, SeleniumSettingsUncheckedCreateInputObjectSchema]) }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsCreateArgs>;
export const SeleniumSettingsCreateOneZodSchema = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), data: z.union([SeleniumSettingsCreateInputObjectSchema, SeleniumSettingsUncheckedCreateInputObjectSchema]) }).strict();
@@ -0,0 +1,7 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
export const LabRxTemplateDeleteManySchema: z.ZodType<Prisma.LabRxTemplateDeleteManyArgs> = z.object({ where: LabRxTemplateWhereInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateDeleteManyArgs>;
export const LabRxTemplateDeleteManyZodSchema = z.object({ where: LabRxTemplateWhereInputObjectSchema.optional() }).strict();
@@ -0,0 +1,7 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
export const SeleniumSettingsDeleteManySchema: z.ZodType<Prisma.SeleniumSettingsDeleteManyArgs> = z.object({ where: SeleniumSettingsWhereInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsDeleteManyArgs>;
export const SeleniumSettingsDeleteManyZodSchema = z.object({ where: SeleniumSettingsWhereInputObjectSchema.optional() }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateSelectObjectSchema as LabRxTemplateSelectObjectSchema } from './objects/LabRxTemplateSelect.schema';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
export const LabRxTemplateDeleteOneSchema: z.ZodType<Prisma.LabRxTemplateDeleteArgs> = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateDeleteArgs>;
export const LabRxTemplateDeleteOneZodSchema = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsSelectObjectSchema as SeleniumSettingsSelectObjectSchema } from './objects/SeleniumSettingsSelect.schema';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
export const SeleniumSettingsDeleteOneSchema: z.ZodType<Prisma.SeleniumSettingsDeleteArgs> = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsDeleteArgs>;
export const SeleniumSettingsDeleteOneZodSchema = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,5 @@
import * as z from 'zod';
export const LabRxTemplateScalarFieldEnumSchema = z.enum(['id', 'userId', 'name', 'labName', 'labPhone', 'labFax', 'labAddress', 'labAccount', 'caseType', 'material', 'instructions', 'sortOrder', 'createdAt', 'updatedAt'])
export type LabRxTemplateScalarFieldEnum = z.infer<typeof LabRxTemplateScalarFieldEnumSchema>;
@@ -0,0 +1,5 @@
import * as z from 'zod';
export const SeleniumSettingsScalarFieldEnumSchema = z.enum(['id', 'userId', 'paymentGroupId'])
export type SeleniumSettingsScalarFieldEnum = z.infer<typeof SeleniumSettingsScalarFieldEnumSchema>;
@@ -0,0 +1,50 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateOrderByWithRelationInputObjectSchema as LabRxTemplateOrderByWithRelationInputObjectSchema } from './objects/LabRxTemplateOrderByWithRelationInput.schema';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
import { LabRxTemplateScalarFieldEnumSchema } from './enums/LabRxTemplateScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const LabRxTemplateFindFirstSelectSchema: z.ZodType<Prisma.LabRxTemplateSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.LabRxTemplateSelect>;
export const LabRxTemplateFindFirstSelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const LabRxTemplateFindFirstSchema: z.ZodType<Prisma.LabRxTemplateFindFirstArgs> = z.object({ select: LabRxTemplateFindFirstSelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateFindFirstArgs>;
export const LabRxTemplateFindFirstZodSchema = z.object({ select: LabRxTemplateFindFirstSelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -0,0 +1,50 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateOrderByWithRelationInputObjectSchema as LabRxTemplateOrderByWithRelationInputObjectSchema } from './objects/LabRxTemplateOrderByWithRelationInput.schema';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
import { LabRxTemplateScalarFieldEnumSchema } from './enums/LabRxTemplateScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const LabRxTemplateFindFirstOrThrowSelectSchema: z.ZodType<Prisma.LabRxTemplateSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.LabRxTemplateSelect>;
export const LabRxTemplateFindFirstOrThrowSelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const LabRxTemplateFindFirstOrThrowSchema: z.ZodType<Prisma.LabRxTemplateFindFirstOrThrowArgs> = z.object({ select: LabRxTemplateFindFirstOrThrowSelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateFindFirstOrThrowArgs>;
export const LabRxTemplateFindFirstOrThrowZodSchema = z.object({ select: LabRxTemplateFindFirstOrThrowSelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -0,0 +1,28 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsOrderByWithRelationInputObjectSchema as SeleniumSettingsOrderByWithRelationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithRelationInput.schema';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
import { SeleniumSettingsScalarFieldEnumSchema } from './enums/SeleniumSettingsScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const SeleniumSettingsFindFirstOrThrowSelectSchema: z.ZodType<Prisma.SeleniumSettingsSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsSelect>;
export const SeleniumSettingsFindFirstOrThrowSelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const SeleniumSettingsFindFirstOrThrowSchema: z.ZodType<Prisma.SeleniumSettingsFindFirstOrThrowArgs> = z.object({ select: SeleniumSettingsFindFirstOrThrowSelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsFindFirstOrThrowArgs>;
export const SeleniumSettingsFindFirstOrThrowZodSchema = z.object({ select: SeleniumSettingsFindFirstOrThrowSelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -41,6 +41,8 @@ export const UserFindFirstOrThrowSelectSchema: z.ZodType<Prisma.UserSelect> = z.
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.UserSelect>; }).strict() as unknown as z.ZodType<Prisma.UserSelect>;
@@ -76,6 +78,8 @@ export const UserFindFirstOrThrowSelectZodSchema = z.object({
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict(); }).strict();
@@ -0,0 +1,28 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsOrderByWithRelationInputObjectSchema as SeleniumSettingsOrderByWithRelationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithRelationInput.schema';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
import { SeleniumSettingsScalarFieldEnumSchema } from './enums/SeleniumSettingsScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const SeleniumSettingsFindFirstSelectSchema: z.ZodType<Prisma.SeleniumSettingsSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsSelect>;
export const SeleniumSettingsFindFirstSelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const SeleniumSettingsFindFirstSchema: z.ZodType<Prisma.SeleniumSettingsFindFirstArgs> = z.object({ select: SeleniumSettingsFindFirstSelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsFindFirstArgs>;
export const SeleniumSettingsFindFirstZodSchema = z.object({ select: SeleniumSettingsFindFirstSelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -41,6 +41,8 @@ export const UserFindFirstSelectSchema: z.ZodType<Prisma.UserSelect> = z.object(
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.UserSelect>; }).strict() as unknown as z.ZodType<Prisma.UserSelect>;
@@ -76,6 +78,8 @@ export const UserFindFirstSelectZodSchema = z.object({
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict(); }).strict();
@@ -0,0 +1,50 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateOrderByWithRelationInputObjectSchema as LabRxTemplateOrderByWithRelationInputObjectSchema } from './objects/LabRxTemplateOrderByWithRelationInput.schema';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
import { LabRxTemplateScalarFieldEnumSchema } from './enums/LabRxTemplateScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const LabRxTemplateFindManySelectSchema: z.ZodType<Prisma.LabRxTemplateSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.LabRxTemplateSelect>;
export const LabRxTemplateFindManySelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
name: z.boolean().optional(),
labName: z.boolean().optional(),
labPhone: z.boolean().optional(),
labFax: z.boolean().optional(),
labAddress: z.boolean().optional(),
labAccount: z.boolean().optional(),
caseType: z.boolean().optional(),
material: z.boolean().optional(),
instructions: z.boolean().optional(),
sortOrder: z.boolean().optional(),
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const LabRxTemplateFindManySchema: z.ZodType<Prisma.LabRxTemplateFindManyArgs> = z.object({ select: LabRxTemplateFindManySelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateFindManyArgs>;
export const LabRxTemplateFindManyZodSchema = z.object({ select: LabRxTemplateFindManySelectSchema.optional(), include: z.lazy(() => LabRxTemplateIncludeObjectSchema.optional()), orderBy: z.union([LabRxTemplateOrderByWithRelationInputObjectSchema, LabRxTemplateOrderByWithRelationInputObjectSchema.array()]).optional(), where: LabRxTemplateWhereInputObjectSchema.optional(), cursor: LabRxTemplateWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([LabRxTemplateScalarFieldEnumSchema, LabRxTemplateScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -0,0 +1,28 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsOrderByWithRelationInputObjectSchema as SeleniumSettingsOrderByWithRelationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithRelationInput.schema';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
import { SeleniumSettingsScalarFieldEnumSchema } from './enums/SeleniumSettingsScalarFieldEnum.schema';
// Select schema needs to be in file to prevent circular imports
//------------------------------------------------------
export const SeleniumSettingsFindManySelectSchema: z.ZodType<Prisma.SeleniumSettingsSelect> = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsSelect>;
export const SeleniumSettingsFindManySelectZodSchema = z.object({
id: z.boolean().optional(),
userId: z.boolean().optional(),
paymentGroupId: z.boolean().optional(),
user: z.boolean().optional()
}).strict();
export const SeleniumSettingsFindManySchema: z.ZodType<Prisma.SeleniumSettingsFindManyArgs> = z.object({ select: SeleniumSettingsFindManySelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsFindManyArgs>;
export const SeleniumSettingsFindManyZodSchema = z.object({ select: SeleniumSettingsFindManySelectSchema.optional(), include: z.lazy(() => SeleniumSettingsIncludeObjectSchema.optional()), orderBy: z.union([SeleniumSettingsOrderByWithRelationInputObjectSchema, SeleniumSettingsOrderByWithRelationInputObjectSchema.array()]).optional(), where: SeleniumSettingsWhereInputObjectSchema.optional(), cursor: SeleniumSettingsWhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.union([SeleniumSettingsScalarFieldEnumSchema, SeleniumSettingsScalarFieldEnumSchema.array()]).optional() }).strict();
@@ -41,6 +41,8 @@ export const UserFindManySelectSchema: z.ZodType<Prisma.UserSelect> = z.object({
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict() as unknown as z.ZodType<Prisma.UserSelect>; }).strict() as unknown as z.ZodType<Prisma.UserSelect>;
@@ -76,6 +78,8 @@ export const UserFindManySelectZodSchema = z.object({
procedureTimeslot: z.boolean().optional(), procedureTimeslot: z.boolean().optional(),
insuranceContacts: z.boolean().optional(), insuranceContacts: z.boolean().optional(),
patientConversations: z.boolean().optional(), patientConversations: z.boolean().optional(),
labRxTemplates: z.boolean().optional(),
seleniumSettings: z.boolean().optional(),
_count: z.boolean().optional() _count: z.boolean().optional()
}).strict(); }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateSelectObjectSchema as LabRxTemplateSelectObjectSchema } from './objects/LabRxTemplateSelect.schema';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
export const LabRxTemplateFindUniqueSchema: z.ZodType<Prisma.LabRxTemplateFindUniqueArgs> = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateFindUniqueArgs>;
export const LabRxTemplateFindUniqueZodSchema = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateSelectObjectSchema as LabRxTemplateSelectObjectSchema } from './objects/LabRxTemplateSelect.schema';
import { LabRxTemplateIncludeObjectSchema as LabRxTemplateIncludeObjectSchema } from './objects/LabRxTemplateInclude.schema';
import { LabRxTemplateWhereUniqueInputObjectSchema as LabRxTemplateWhereUniqueInputObjectSchema } from './objects/LabRxTemplateWhereUniqueInput.schema';
export const LabRxTemplateFindUniqueOrThrowSchema: z.ZodType<Prisma.LabRxTemplateFindUniqueOrThrowArgs> = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateFindUniqueOrThrowArgs>;
export const LabRxTemplateFindUniqueOrThrowZodSchema = z.object({ select: LabRxTemplateSelectObjectSchema.optional(), include: LabRxTemplateIncludeObjectSchema.optional(), where: LabRxTemplateWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsSelectObjectSchema as SeleniumSettingsSelectObjectSchema } from './objects/SeleniumSettingsSelect.schema';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
export const SeleniumSettingsFindUniqueOrThrowSchema: z.ZodType<Prisma.SeleniumSettingsFindUniqueOrThrowArgs> = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsFindUniqueOrThrowArgs>;
export const SeleniumSettingsFindUniqueOrThrowZodSchema = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,9 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsSelectObjectSchema as SeleniumSettingsSelectObjectSchema } from './objects/SeleniumSettingsSelect.schema';
import { SeleniumSettingsIncludeObjectSchema as SeleniumSettingsIncludeObjectSchema } from './objects/SeleniumSettingsInclude.schema';
import { SeleniumSettingsWhereUniqueInputObjectSchema as SeleniumSettingsWhereUniqueInputObjectSchema } from './objects/SeleniumSettingsWhereUniqueInput.schema';
export const SeleniumSettingsFindUniqueSchema: z.ZodType<Prisma.SeleniumSettingsFindUniqueArgs> = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsFindUniqueArgs>;
export const SeleniumSettingsFindUniqueZodSchema = z.object({ select: SeleniumSettingsSelectObjectSchema.optional(), include: SeleniumSettingsIncludeObjectSchema.optional(), where: SeleniumSettingsWhereUniqueInputObjectSchema }).strict();
@@ -0,0 +1,15 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { LabRxTemplateWhereInputObjectSchema as LabRxTemplateWhereInputObjectSchema } from './objects/LabRxTemplateWhereInput.schema';
import { LabRxTemplateOrderByWithAggregationInputObjectSchema as LabRxTemplateOrderByWithAggregationInputObjectSchema } from './objects/LabRxTemplateOrderByWithAggregationInput.schema';
import { LabRxTemplateScalarWhereWithAggregatesInputObjectSchema as LabRxTemplateScalarWhereWithAggregatesInputObjectSchema } from './objects/LabRxTemplateScalarWhereWithAggregatesInput.schema';
import { LabRxTemplateScalarFieldEnumSchema } from './enums/LabRxTemplateScalarFieldEnum.schema';
import { LabRxTemplateCountAggregateInputObjectSchema as LabRxTemplateCountAggregateInputObjectSchema } from './objects/LabRxTemplateCountAggregateInput.schema';
import { LabRxTemplateMinAggregateInputObjectSchema as LabRxTemplateMinAggregateInputObjectSchema } from './objects/LabRxTemplateMinAggregateInput.schema';
import { LabRxTemplateMaxAggregateInputObjectSchema as LabRxTemplateMaxAggregateInputObjectSchema } from './objects/LabRxTemplateMaxAggregateInput.schema';
import { LabRxTemplateAvgAggregateInputObjectSchema as LabRxTemplateAvgAggregateInputObjectSchema } from './objects/LabRxTemplateAvgAggregateInput.schema';
import { LabRxTemplateSumAggregateInputObjectSchema as LabRxTemplateSumAggregateInputObjectSchema } from './objects/LabRxTemplateSumAggregateInput.schema';
export const LabRxTemplateGroupBySchema: z.ZodType<Prisma.LabRxTemplateGroupByArgs> = z.object({ where: LabRxTemplateWhereInputObjectSchema.optional(), orderBy: z.union([LabRxTemplateOrderByWithAggregationInputObjectSchema, LabRxTemplateOrderByWithAggregationInputObjectSchema.array()]).optional(), having: LabRxTemplateScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(LabRxTemplateScalarFieldEnumSchema), _count: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional(), _min: LabRxTemplateMinAggregateInputObjectSchema.optional(), _max: LabRxTemplateMaxAggregateInputObjectSchema.optional(), _avg: LabRxTemplateAvgAggregateInputObjectSchema.optional(), _sum: LabRxTemplateSumAggregateInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.LabRxTemplateGroupByArgs>;
export const LabRxTemplateGroupByZodSchema = z.object({ where: LabRxTemplateWhereInputObjectSchema.optional(), orderBy: z.union([LabRxTemplateOrderByWithAggregationInputObjectSchema, LabRxTemplateOrderByWithAggregationInputObjectSchema.array()]).optional(), having: LabRxTemplateScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(LabRxTemplateScalarFieldEnumSchema), _count: z.union([ z.literal(true), LabRxTemplateCountAggregateInputObjectSchema ]).optional(), _min: LabRxTemplateMinAggregateInputObjectSchema.optional(), _max: LabRxTemplateMaxAggregateInputObjectSchema.optional(), _avg: LabRxTemplateAvgAggregateInputObjectSchema.optional(), _sum: LabRxTemplateSumAggregateInputObjectSchema.optional() }).strict();
@@ -0,0 +1,15 @@
import type { Prisma } from '../../generated/prisma';
import * as z from 'zod';
import { SeleniumSettingsWhereInputObjectSchema as SeleniumSettingsWhereInputObjectSchema } from './objects/SeleniumSettingsWhereInput.schema';
import { SeleniumSettingsOrderByWithAggregationInputObjectSchema as SeleniumSettingsOrderByWithAggregationInputObjectSchema } from './objects/SeleniumSettingsOrderByWithAggregationInput.schema';
import { SeleniumSettingsScalarWhereWithAggregatesInputObjectSchema as SeleniumSettingsScalarWhereWithAggregatesInputObjectSchema } from './objects/SeleniumSettingsScalarWhereWithAggregatesInput.schema';
import { SeleniumSettingsScalarFieldEnumSchema } from './enums/SeleniumSettingsScalarFieldEnum.schema';
import { SeleniumSettingsCountAggregateInputObjectSchema as SeleniumSettingsCountAggregateInputObjectSchema } from './objects/SeleniumSettingsCountAggregateInput.schema';
import { SeleniumSettingsMinAggregateInputObjectSchema as SeleniumSettingsMinAggregateInputObjectSchema } from './objects/SeleniumSettingsMinAggregateInput.schema';
import { SeleniumSettingsMaxAggregateInputObjectSchema as SeleniumSettingsMaxAggregateInputObjectSchema } from './objects/SeleniumSettingsMaxAggregateInput.schema';
import { SeleniumSettingsAvgAggregateInputObjectSchema as SeleniumSettingsAvgAggregateInputObjectSchema } from './objects/SeleniumSettingsAvgAggregateInput.schema';
import { SeleniumSettingsSumAggregateInputObjectSchema as SeleniumSettingsSumAggregateInputObjectSchema } from './objects/SeleniumSettingsSumAggregateInput.schema';
export const SeleniumSettingsGroupBySchema: z.ZodType<Prisma.SeleniumSettingsGroupByArgs> = z.object({ where: SeleniumSettingsWhereInputObjectSchema.optional(), orderBy: z.union([SeleniumSettingsOrderByWithAggregationInputObjectSchema, SeleniumSettingsOrderByWithAggregationInputObjectSchema.array()]).optional(), having: SeleniumSettingsScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(SeleniumSettingsScalarFieldEnumSchema), _count: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional(), _min: SeleniumSettingsMinAggregateInputObjectSchema.optional(), _max: SeleniumSettingsMaxAggregateInputObjectSchema.optional(), _avg: SeleniumSettingsAvgAggregateInputObjectSchema.optional(), _sum: SeleniumSettingsSumAggregateInputObjectSchema.optional() }).strict() as unknown as z.ZodType<Prisma.SeleniumSettingsGroupByArgs>;
export const SeleniumSettingsGroupByZodSchema = z.object({ where: SeleniumSettingsWhereInputObjectSchema.optional(), orderBy: z.union([SeleniumSettingsOrderByWithAggregationInputObjectSchema, SeleniumSettingsOrderByWithAggregationInputObjectSchema.array()]).optional(), having: SeleniumSettingsScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(SeleniumSettingsScalarFieldEnumSchema), _count: z.union([ z.literal(true), SeleniumSettingsCountAggregateInputObjectSchema ]).optional(), _min: SeleniumSettingsMinAggregateInputObjectSchema.optional(), _max: SeleniumSettingsMaxAggregateInputObjectSchema.optional(), _avg: SeleniumSettingsAvgAggregateInputObjectSchema.optional(), _sum: SeleniumSettingsSumAggregateInputObjectSchema.optional() }).strict();
+68
View File
@@ -31,8 +31,10 @@ export * from './enums/OfficeContactScalarFieldEnum.schema'
export * from './enums/InsuranceContactScalarFieldEnum.schema' export * from './enums/InsuranceContactScalarFieldEnum.schema'
export * from './enums/ProcedureTimeslotScalarFieldEnum.schema' export * from './enums/ProcedureTimeslotScalarFieldEnum.schema'
export * from './enums/PatientConversationScalarFieldEnum.schema' export * from './enums/PatientConversationScalarFieldEnum.schema'
export * from './enums/LabRxTemplateScalarFieldEnum.schema'
export * from './enums/CommissionBatchScalarFieldEnum.schema' export * from './enums/CommissionBatchScalarFieldEnum.schema'
export * from './enums/CommissionBatchItemScalarFieldEnum.schema' export * from './enums/CommissionBatchItemScalarFieldEnum.schema'
export * from './enums/SeleniumSettingsScalarFieldEnum.schema'
export * from './enums/SortOrder.schema' export * from './enums/SortOrder.schema'
export * from './enums/NullableJsonNullValueInput.schema' export * from './enums/NullableJsonNullValueInput.schema'
export * from './enums/JsonNullValueInput.schema' export * from './enums/JsonNullValueInput.schema'
@@ -595,6 +597,23 @@ export * from './updateManyAndReturnPatientConversation.schema'
export * from './upsertOnePatientConversation.schema' export * from './upsertOnePatientConversation.schema'
export * from './aggregatePatientConversation.schema' export * from './aggregatePatientConversation.schema'
export * from './groupByPatientConversation.schema' export * from './groupByPatientConversation.schema'
export * from './findUniqueLabRxTemplate.schema'
export * from './findUniqueOrThrowLabRxTemplate.schema'
export * from './findFirstLabRxTemplate.schema'
export * from './findFirstOrThrowLabRxTemplate.schema'
export * from './findManyLabRxTemplate.schema'
export * from './countLabRxTemplate.schema'
export * from './createOneLabRxTemplate.schema'
export * from './createManyLabRxTemplate.schema'
export * from './createManyAndReturnLabRxTemplate.schema'
export * from './deleteOneLabRxTemplate.schema'
export * from './deleteManyLabRxTemplate.schema'
export * from './updateOneLabRxTemplate.schema'
export * from './updateManyLabRxTemplate.schema'
export * from './updateManyAndReturnLabRxTemplate.schema'
export * from './upsertOneLabRxTemplate.schema'
export * from './aggregateLabRxTemplate.schema'
export * from './groupByLabRxTemplate.schema'
export * from './findUniqueCommissionBatch.schema' export * from './findUniqueCommissionBatch.schema'
export * from './findUniqueOrThrowCommissionBatch.schema' export * from './findUniqueOrThrowCommissionBatch.schema'
export * from './findFirstCommissionBatch.schema' export * from './findFirstCommissionBatch.schema'
@@ -629,6 +648,23 @@ export * from './updateManyAndReturnCommissionBatchItem.schema'
export * from './upsertOneCommissionBatchItem.schema' export * from './upsertOneCommissionBatchItem.schema'
export * from './aggregateCommissionBatchItem.schema' export * from './aggregateCommissionBatchItem.schema'
export * from './groupByCommissionBatchItem.schema' export * from './groupByCommissionBatchItem.schema'
export * from './findUniqueSeleniumSettings.schema'
export * from './findUniqueOrThrowSeleniumSettings.schema'
export * from './findFirstSeleniumSettings.schema'
export * from './findFirstOrThrowSeleniumSettings.schema'
export * from './findManySeleniumSettings.schema'
export * from './countSeleniumSettings.schema'
export * from './createOneSeleniumSettings.schema'
export * from './createManySeleniumSettings.schema'
export * from './createManyAndReturnSeleniumSettings.schema'
export * from './deleteOneSeleniumSettings.schema'
export * from './deleteManySeleniumSettings.schema'
export * from './updateOneSeleniumSettings.schema'
export * from './updateManySeleniumSettings.schema'
export * from './updateManyAndReturnSeleniumSettings.schema'
export * from './upsertOneSeleniumSettings.schema'
export * from './aggregateSeleniumSettings.schema'
export * from './groupBySeleniumSettings.schema'
export * from './results/UserFindUniqueResult.schema' export * from './results/UserFindUniqueResult.schema'
export * from './results/UserFindFirstResult.schema' export * from './results/UserFindFirstResult.schema'
export * from './results/UserFindManyResult.schema' export * from './results/UserFindManyResult.schema'
@@ -1045,6 +1081,19 @@ export * from './results/PatientConversationDeleteManyResult.schema'
export * from './results/PatientConversationAggregateResult.schema' export * from './results/PatientConversationAggregateResult.schema'
export * from './results/PatientConversationGroupByResult.schema' export * from './results/PatientConversationGroupByResult.schema'
export * from './results/PatientConversationCountResult.schema' export * from './results/PatientConversationCountResult.schema'
export * from './results/LabRxTemplateFindUniqueResult.schema'
export * from './results/LabRxTemplateFindFirstResult.schema'
export * from './results/LabRxTemplateFindManyResult.schema'
export * from './results/LabRxTemplateCreateResult.schema'
export * from './results/LabRxTemplateCreateManyResult.schema'
export * from './results/LabRxTemplateUpdateResult.schema'
export * from './results/LabRxTemplateUpdateManyResult.schema'
export * from './results/LabRxTemplateUpsertResult.schema'
export * from './results/LabRxTemplateDeleteResult.schema'
export * from './results/LabRxTemplateDeleteManyResult.schema'
export * from './results/LabRxTemplateAggregateResult.schema'
export * from './results/LabRxTemplateGroupByResult.schema'
export * from './results/LabRxTemplateCountResult.schema'
export * from './results/CommissionBatchFindUniqueResult.schema' export * from './results/CommissionBatchFindUniqueResult.schema'
export * from './results/CommissionBatchFindFirstResult.schema' export * from './results/CommissionBatchFindFirstResult.schema'
export * from './results/CommissionBatchFindManyResult.schema' export * from './results/CommissionBatchFindManyResult.schema'
@@ -1071,6 +1120,19 @@ export * from './results/CommissionBatchItemDeleteManyResult.schema'
export * from './results/CommissionBatchItemAggregateResult.schema' export * from './results/CommissionBatchItemAggregateResult.schema'
export * from './results/CommissionBatchItemGroupByResult.schema' export * from './results/CommissionBatchItemGroupByResult.schema'
export * from './results/CommissionBatchItemCountResult.schema' export * from './results/CommissionBatchItemCountResult.schema'
export * from './results/SeleniumSettingsFindUniqueResult.schema'
export * from './results/SeleniumSettingsFindFirstResult.schema'
export * from './results/SeleniumSettingsFindManyResult.schema'
export * from './results/SeleniumSettingsCreateResult.schema'
export * from './results/SeleniumSettingsCreateManyResult.schema'
export * from './results/SeleniumSettingsUpdateResult.schema'
export * from './results/SeleniumSettingsUpdateManyResult.schema'
export * from './results/SeleniumSettingsUpsertResult.schema'
export * from './results/SeleniumSettingsDeleteResult.schema'
export * from './results/SeleniumSettingsDeleteManyResult.schema'
export * from './results/SeleniumSettingsAggregateResult.schema'
export * from './results/SeleniumSettingsGroupByResult.schema'
export * from './results/SeleniumSettingsCountResult.schema'
export * from './results/index' export * from './results/index'
export * from './objects/index' export * from './objects/index'
export * from './variants/pure/User.pure' export * from './variants/pure/User.pure'
@@ -1105,8 +1167,10 @@ export * from './variants/pure/OfficeContact.pure'
export * from './variants/pure/InsuranceContact.pure' export * from './variants/pure/InsuranceContact.pure'
export * from './variants/pure/ProcedureTimeslot.pure' export * from './variants/pure/ProcedureTimeslot.pure'
export * from './variants/pure/PatientConversation.pure' export * from './variants/pure/PatientConversation.pure'
export * from './variants/pure/LabRxTemplate.pure'
export * from './variants/pure/CommissionBatch.pure' export * from './variants/pure/CommissionBatch.pure'
export * from './variants/pure/CommissionBatchItem.pure' export * from './variants/pure/CommissionBatchItem.pure'
export * from './variants/pure/SeleniumSettings.pure'
export * from './variants/pure/index' export * from './variants/pure/index'
export * from './variants/input/User.input' export * from './variants/input/User.input'
export * from './variants/input/Patient.input' export * from './variants/input/Patient.input'
@@ -1140,8 +1204,10 @@ export * from './variants/input/OfficeContact.input'
export * from './variants/input/InsuranceContact.input' export * from './variants/input/InsuranceContact.input'
export * from './variants/input/ProcedureTimeslot.input' export * from './variants/input/ProcedureTimeslot.input'
export * from './variants/input/PatientConversation.input' export * from './variants/input/PatientConversation.input'
export * from './variants/input/LabRxTemplate.input'
export * from './variants/input/CommissionBatch.input' export * from './variants/input/CommissionBatch.input'
export * from './variants/input/CommissionBatchItem.input' export * from './variants/input/CommissionBatchItem.input'
export * from './variants/input/SeleniumSettings.input'
export * from './variants/input/index' export * from './variants/input/index'
export * from './variants/result/User.result' export * from './variants/result/User.result'
export * from './variants/result/Patient.result' export * from './variants/result/Patient.result'
@@ -1175,7 +1241,9 @@ export * from './variants/result/OfficeContact.result'
export * from './variants/result/InsuranceContact.result' export * from './variants/result/InsuranceContact.result'
export * from './variants/result/ProcedureTimeslot.result' export * from './variants/result/ProcedureTimeslot.result'
export * from './variants/result/PatientConversation.result' export * from './variants/result/PatientConversation.result'
export * from './variants/result/LabRxTemplate.result'
export * from './variants/result/CommissionBatch.result' export * from './variants/result/CommissionBatch.result'
export * from './variants/result/CommissionBatchItem.result' export * from './variants/result/CommissionBatchItem.result'
export * from './variants/result/SeleniumSettings.result'
export * from './variants/result/index' export * from './variants/result/index'
export * from './variants/index' export * from './variants/index'
@@ -1,12 +1,13 @@
import * as z from 'zod'; import * as z from 'zod';
import { Prisma } from '../../../generated/prisma'; import type { Prisma } from '../../../generated/prisma';
import Decimal from 'decimal.js';
import { ProcedureSourceSchema } from '../enums/ProcedureSource.schema'; import { ProcedureSourceSchema } from '../enums/ProcedureSource.schema';
import { AppointmentCreateNestedOneWithoutProceduresInputObjectSchema as AppointmentCreateNestedOneWithoutProceduresInputObjectSchema } from './AppointmentCreateNestedOneWithoutProceduresInput.schema'; import { AppointmentCreateNestedOneWithoutProceduresInputObjectSchema as AppointmentCreateNestedOneWithoutProceduresInputObjectSchema } from './AppointmentCreateNestedOneWithoutProceduresInput.schema';
import { PatientCreateNestedOneWithoutProceduresInputObjectSchema as PatientCreateNestedOneWithoutProceduresInputObjectSchema } from './PatientCreateNestedOneWithoutProceduresInput.schema'; import { PatientCreateNestedOneWithoutProceduresInputObjectSchema as PatientCreateNestedOneWithoutProceduresInputObjectSchema } from './PatientCreateNestedOneWithoutProceduresInput.schema';
import { NpiProviderCreateNestedOneWithoutAppointmentProceduresInputObjectSchema as NpiProviderCreateNestedOneWithoutAppointmentProceduresInputObjectSchema } from './NpiProviderCreateNestedOneWithoutAppointmentProceduresInput.schema' import { NpiProviderCreateNestedOneWithoutAppointmentProceduresInputObjectSchema as NpiProviderCreateNestedOneWithoutAppointmentProceduresInputObjectSchema } from './NpiProviderCreateNestedOneWithoutAppointmentProceduresInput.schema'
import { DecimalJSLikeSchema, isValidDecimalInput } from '../../helpers/decimal-helpers'; import { DecimalJSLikeSchema, isValidDecimalInput } from '../../helpers/decimal-helpers';
import Decimal from "decimal.js";
const makeSchema = () => z.object({ const makeSchema = () => z.object({
procedureCode: z.string(), procedureCode: z.string(),
procedureLabel: z.string().optional().nullable(), procedureLabel: z.string().optional().nullable(),
@@ -14,7 +15,7 @@ const makeSchema = () => z.object({
z.number(), z.number(),
z.string(), z.string(),
z.instanceof(Decimal), z.instanceof(Decimal),
z.instanceof(Prisma.Decimal), z.instanceof(Decimal),
DecimalJSLikeSchema, DecimalJSLikeSchema,
]).refine((v) => isValidDecimalInput(v), { ]).refine((v) => isValidDecimalInput(v), {
message: "Field 'fee' must be a Decimal", message: "Field 'fee' must be a Decimal",
@@ -1,9 +1,10 @@
import * as z from 'zod'; import * as z from 'zod';
import { Prisma } from '../../../generated/prisma'; import type { Prisma } from '../../../generated/prisma';
import Decimal from 'decimal.js';
import { ProcedureSourceSchema } from '../enums/ProcedureSource.schema' import { ProcedureSourceSchema } from '../enums/ProcedureSource.schema'
import { DecimalJSLikeSchema, isValidDecimalInput } from '../../helpers/decimal-helpers'; import { DecimalJSLikeSchema, isValidDecimalInput } from '../../helpers/decimal-helpers';
import Decimal from "decimal.js";
const makeSchema = () => z.object({ const makeSchema = () => z.object({
id: z.number().int().optional(), id: z.number().int().optional(),
patientId: z.number().int(), patientId: z.number().int(),
@@ -14,7 +15,7 @@ const makeSchema = () => z.object({
z.number(), z.number(),
z.string(), z.string(),
z.instanceof(Decimal), z.instanceof(Decimal),
z.instanceof(Prisma.Decimal), z.instanceof(Decimal),
DecimalJSLikeSchema, DecimalJSLikeSchema,
]).refine((v) => isValidDecimalInput(v), { ]).refine((v) => isValidDecimalInput(v), {
message: "Field 'fee' must be a Decimal", message: "Field 'fee' must be a Decimal",

Some files were not shown because too many files have changed in this diff Show More