feat: improve CCA preauth cell filling, implants category, preauth no recording
- Selenium: bulletproof Wait→Click→Clear→Type→Verify for tooth, billed amt cells - Selenium: fix billed amt to click td[23] (correct column) to trigger edit mode - Selenium: skip tentative date (auto-filled by page after tooth entry) - Frontend: add Implants category with Implant/Abut/Crown, Fixture, Abutment, Crown buttons (D6010/D6057/D6058) - Frontend: pdf-preview-modal renders PNG screenshots as <img> instead of PDF iframe - Backend: CCA preauth route creates claim record if none exists - Backend: CCA preauth processor saves authNumber into claimNumber column after submission Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,7 @@ interface ClaimFormProps {
|
||||
onHandleForMHSeleniumClaim: (data: ClaimFormData) => void;
|
||||
onHandleForMHSeleniumClaimPreAuth: (data: ClaimPreAuthData) => void;
|
||||
onHandleForCCASeleniumClaim: (data: ClaimFormData) => void;
|
||||
onHandleForCCASeleniumPreAuth: (data: ClaimPreAuthData) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -89,6 +90,7 @@ export function ClaimForm({
|
||||
onHandleForMHSeleniumClaim,
|
||||
onHandleForMHSeleniumClaimPreAuth,
|
||||
onHandleForCCASeleniumClaim,
|
||||
onHandleForCCASeleniumPreAuth,
|
||||
onSubmit,
|
||||
onClose,
|
||||
}: ClaimFormProps) {
|
||||
@@ -974,6 +976,44 @@ export function ClaimForm({
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCCAPreAuth = async () => {
|
||||
const missingFields: string[] = [];
|
||||
if (!form.memberId?.trim()) missingFields.push("Member ID");
|
||||
if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth");
|
||||
if (!patient?.firstName?.trim()) missingFields.push("First Name");
|
||||
if (missingFields.length > 0) {
|
||||
toast({
|
||||
title: "Missing Required Fields",
|
||||
description: `Please fill out the following field(s): ${missingFields.join(", ")}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredServiceLines = (form.serviceLines || []).filter(
|
||||
(line) => (line.procedureCode ?? "").trim() !== "",
|
||||
);
|
||||
if (filteredServiceLines.length === 0) {
|
||||
toast({
|
||||
title: "No procedure codes",
|
||||
description: "Please add at least one procedure code before submitting the pre-authorization.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onHandleForCCASeleniumPreAuth({
|
||||
...form,
|
||||
serviceLines: filteredServiceLines,
|
||||
staffId: appointmentStaffId ?? Number(staff?.id),
|
||||
patientId,
|
||||
insuranceProvider: "CCA",
|
||||
insuranceSiteKey: "CCA",
|
||||
});
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
const uploadAttachmentsToLocalFolder = async (files: File[]): Promise<ClaimFileMeta[]> => {
|
||||
if (!files.length) return [];
|
||||
|
||||
@@ -1824,7 +1864,7 @@ export function ClaimForm({
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
<Button
|
||||
className="w-32 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={() => handleMHSubmit()}
|
||||
@@ -1837,8 +1877,14 @@ export function ClaimForm({
|
||||
>
|
||||
CCA Claim
|
||||
</Button>
|
||||
<Button className="w-36" variant="outline">
|
||||
Delta MA Claim
|
||||
</Button>
|
||||
<Button className="w-44" variant="outline">
|
||||
United/DentalHub Claim
|
||||
</Button>
|
||||
<Button className="w-32" variant="outline">
|
||||
Delta MA
|
||||
Tufts Claim
|
||||
</Button>
|
||||
<Button
|
||||
className="w-36 bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
@@ -1954,7 +2000,7 @@ export function ClaimForm({
|
||||
<SelectItem value="Others">Others</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Label className="flex items-center">Service Date</Label>
|
||||
<Label className="flex items-center">Tentative Service Date</Label>
|
||||
<Popover
|
||||
open={serviceDateOpen}
|
||||
onOpenChange={setServiceDateOpen}
|
||||
@@ -2342,14 +2388,31 @@ export function ClaimForm({
|
||||
<h3 className="text-xl font-semibold mb-4 text-center">
|
||||
PreAuth
|
||||
</h3>
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
<Button
|
||||
className="w-32"
|
||||
variant="secondary"
|
||||
className="w-32 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={() => handleMHPreAuth()}
|
||||
>
|
||||
MH PreAuth
|
||||
</Button>
|
||||
<Button
|
||||
className="w-32 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={handleCCAPreAuth}
|
||||
>
|
||||
CCA PreAuth
|
||||
</Button>
|
||||
<Button
|
||||
className="w-44"
|
||||
variant="secondary"
|
||||
>
|
||||
United/DentalHub PreAuth
|
||||
</Button>
|
||||
<Button
|
||||
className="w-32"
|
||||
variant="secondary"
|
||||
>
|
||||
Tufts PreAuth
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,7 @@ export function PdfPreviewModal({
|
||||
autoDownload = false,
|
||||
}: Props) {
|
||||
const [fileBlobUrl, setFileBlobUrl] = useState<string | null>(null);
|
||||
const [isImage, setIsImage] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [resolvedFilename, setResolvedFilename] = useState<string | null>(null);
|
||||
@@ -98,8 +99,13 @@ export function PdfPreviewModal({
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
if (aborted) return;
|
||||
|
||||
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
||||
const lowerName = finalName.toLowerCase();
|
||||
const isPng = lowerName.endsWith(".png");
|
||||
const isJpg = lowerName.endsWith(".jpg") || lowerName.endsWith(".jpeg");
|
||||
const mimeType = isPng ? "image/png" : isJpg ? "image/jpeg" : "application/pdf";
|
||||
const blob = new Blob([arrayBuffer], { type: mimeType });
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setIsImage(isPng || isJpg);
|
||||
setFileBlobUrl(objectUrl);
|
||||
|
||||
if (autoDownload) {
|
||||
@@ -132,6 +138,7 @@ export function PdfPreviewModal({
|
||||
controller.abort();
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
||||
setFileBlobUrl(null);
|
||||
setIsImage(false);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
setResolvedFilename(null);
|
||||
@@ -194,12 +201,20 @@ export function PdfPreviewModal({
|
||||
{loading && <div>Loading PDF…</div>}
|
||||
{error && <div className="text-destructive">Error: {error}</div>}
|
||||
{fileBlobUrl && (
|
||||
<iframe
|
||||
title="PDF Preview"
|
||||
src={fileBlobUrl}
|
||||
className="w-full h-full border"
|
||||
style={{ minHeight: 0 }}
|
||||
/>
|
||||
isImage ? (
|
||||
<img
|
||||
src={fileBlobUrl}
|
||||
alt={resolvedFilename ?? "Preview"}
|
||||
className="max-w-full max-h-full object-contain mx-auto"
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
title="PDF Preview"
|
||||
src={fileBlobUrl}
|
||||
className="w-full h-full border"
|
||||
style={{ minHeight: 0 }}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -454,6 +454,34 @@ export default function ClaimsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// CCA pre-auth selenium handler
|
||||
const handleCCAPreAuthSubmitSelenium = async (data: any) => {
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(data));
|
||||
if (socketId) formData.append("socketId", socketId);
|
||||
const uploadedFiles: File[] = data.uploadedFiles ?? [];
|
||||
uploadedFiles.forEach((file: File) => {
|
||||
if (file.type === "application/pdf") {
|
||||
formData.append("pdfs", file);
|
||||
} else if (file.type.startsWith("image/")) {
|
||||
formData.append("images", file);
|
||||
}
|
||||
});
|
||||
try {
|
||||
dispatch(setTaskStatus({ key: "claimSubmit", status: "pending", message: "Submitting CCA PreAuth..." }));
|
||||
const response = await apiRequest("POST", "/api/claims/cca-preauth", formData);
|
||||
const result = await response.json();
|
||||
if (result.error) throw new Error(result.error);
|
||||
pendingClaimMeta.current = { patientId: selectedPatientId, groupKey: "INSURANCE_CLAIM_PREAUTH" };
|
||||
setPendingClaimJobId(result.jobId);
|
||||
dispatch(setTaskStatus({ key: "claimSubmit", status: "pending", message: "CCA PreAuth queued. Awaiting Selenium..." }));
|
||||
toast({ title: "CCA PreAuth queued", description: "Selenium is processing the pre-authorization.", variant: "default" });
|
||||
} catch (error: any) {
|
||||
dispatch(setTaskStatus({ key: "claimSubmit", status: "error", message: error.message || "CCA PreAuth failed" }));
|
||||
toast({ title: "CCA PreAuth error", description: error.message || "An error occurred.", variant: "destructive" });
|
||||
}
|
||||
};
|
||||
|
||||
// 5. selenium pdf download handler
|
||||
const handleMHSeleniumPdfDownload = async (
|
||||
data: any,
|
||||
@@ -666,6 +694,7 @@ export default function ClaimsPage() {
|
||||
onHandleForMHSeleniumClaim={handleMHClaimSubmitSelenium}
|
||||
onHandleForMHSeleniumClaimPreAuth={handleMHClaimPreAuthSubmitSelenium}
|
||||
onHandleForCCASeleniumClaim={handleCCAClaimSubmitSelenium}
|
||||
onHandleForCCASeleniumPreAuth={handleCCAPreAuthSubmitSelenium}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -233,6 +233,28 @@ export const PROCEDURE_COMBOS: Record<
|
||||
codes: ["D5214"],
|
||||
},
|
||||
|
||||
// Implants
|
||||
implantFull: {
|
||||
id: "implantFull",
|
||||
label: "Implant/Abut/Crown",
|
||||
codes: ["D6010", "D6057", "D6058"],
|
||||
},
|
||||
implantFixture: {
|
||||
id: "implantFixture",
|
||||
label: "Implant Fixture",
|
||||
codes: ["D6010"],
|
||||
},
|
||||
implantAbutment: {
|
||||
id: "implantAbutment",
|
||||
label: "Abutment",
|
||||
codes: ["D6057"],
|
||||
},
|
||||
implantCrown: {
|
||||
id: "implantCrown",
|
||||
label: "Implant Crown",
|
||||
codes: ["D6058"],
|
||||
},
|
||||
|
||||
// Endodontics
|
||||
rctAnterior: {
|
||||
id: "rctAnterior",
|
||||
@@ -357,6 +379,7 @@ export const COMBO_CATEGORIES: Record<
|
||||
"plResin",
|
||||
"plCast",
|
||||
],
|
||||
Implants: ["implantFull", "implantFixture", "implantAbutment", "implantCrown"],
|
||||
Endodontics: ["rctAnterior", "rctPremolar", "rctMolar", "postCore", "coreBU"],
|
||||
Prosthodontics: ["crown"],
|
||||
Periodontics: ["deepCleaning"],
|
||||
|
||||
Reference in New Issue
Block a user