feat(claimFilesMetadata) - Added feature for having claims uploaded files metadata
This commit is contained in:
@@ -246,6 +246,18 @@ router.get("/:id", async (req: Request, res: Response): Promise<any> => {
|
||||
// Create a new claim
|
||||
router.post("/", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
// --- TRANSFORM claimFiles (if provided) into Prisma nested-create shape
|
||||
if (Array.isArray(req.body.claimFiles)) {
|
||||
// each item expected: { filename: string, mimeType: string }
|
||||
req.body.claimFiles = {
|
||||
create: req.body.claimFiles.map((f: any) => ({
|
||||
filename: String(f.filename),
|
||||
mimeType: String(f.mimeType || f.mime || ""),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSFORM serviceLines
|
||||
if (Array.isArray(req.body.serviceLines)) {
|
||||
req.body.serviceLines = req.body.serviceLines.map(
|
||||
(line: InputServiceLine) => ({
|
||||
|
||||
@@ -578,6 +578,7 @@ export const storage: IStorage = {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
staff: true,
|
||||
claimFiles: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -600,7 +601,7 @@ export const storage: IStorage = {
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
include: { serviceLines: true, staff: true },
|
||||
include: { serviceLines: true, staff: true, claimFiles: true },
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@ import {
|
||||
} from "@/utils/procedureCombosMapping";
|
||||
import { COMBO_CATEGORIES, PROCEDURE_COMBOS } from "@/utils/procedureCombos";
|
||||
|
||||
interface ClaimFileMeta {
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
interface ClaimFormData {
|
||||
patientId: number;
|
||||
appointmentId: number;
|
||||
@@ -63,6 +68,7 @@ interface ClaimFormData {
|
||||
status: string; // default "pending"
|
||||
serviceLines: InputServiceLine[];
|
||||
claimId?: number;
|
||||
claimFiles?: ClaimFileMeta[];
|
||||
}
|
||||
|
||||
interface ClaimFormProps {
|
||||
@@ -341,6 +347,13 @@ export function ClaimForm({
|
||||
(line) => line.procedureCode.trim() !== ""
|
||||
);
|
||||
const { uploadedFiles, insuranceSiteKey, ...formToCreateClaim } = form;
|
||||
|
||||
// build claimFiles metadata from uploadedFiles (only filename + mimeType)
|
||||
const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({
|
||||
filename: f.name,
|
||||
mimeType: f.type,
|
||||
}));
|
||||
|
||||
const createdClaim = await onSubmit({
|
||||
...formToCreateClaim,
|
||||
serviceLines: filteredServiceLines,
|
||||
@@ -348,6 +361,7 @@ export function ClaimForm({
|
||||
patientId: patientId,
|
||||
insuranceProvider: "MassHealth",
|
||||
appointmentId: appointmentId!,
|
||||
claimFiles: claimFilesMeta,
|
||||
});
|
||||
|
||||
// 4. sending form data to selenium service
|
||||
@@ -415,6 +429,13 @@ export function ClaimForm({
|
||||
(line) => line.procedureCode.trim() !== ""
|
||||
);
|
||||
const { uploadedFiles, insuranceSiteKey, ...formToCreateClaim } = form;
|
||||
|
||||
// build claimFiles metadata from uploadedFiles (only filename + mimeType)
|
||||
const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({
|
||||
filename: f.name,
|
||||
mimeType: f.type,
|
||||
}));
|
||||
|
||||
const createdClaim = await onSubmit({
|
||||
...formToCreateClaim,
|
||||
serviceLines: filteredServiceLines,
|
||||
@@ -422,6 +443,7 @@ export function ClaimForm({
|
||||
patientId: patientId,
|
||||
insuranceProvider: "MassHealth",
|
||||
appointmentId: appointmentId!,
|
||||
claimFiles: claimFilesMeta,
|
||||
});
|
||||
|
||||
// 4. Close form
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React from "react";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
import { ClaimWithServiceLines } from "@repo/db/types";
|
||||
import { ClaimFileMeta, ClaimWithServiceLines } from "@repo/db/types";
|
||||
import { FileText, Paperclip } from "lucide-react";
|
||||
|
||||
type ClaimViewModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -25,6 +26,40 @@ export default function ClaimViewModal({
|
||||
claim,
|
||||
onEditClaim,
|
||||
}: ClaimViewModalProps) {
|
||||
// Normalizer: supports both ClaimFile[] and nested-create shape { create: ClaimFile[] }
|
||||
const getClaimFilesArray = (
|
||||
c: ClaimWithServiceLines | null
|
||||
): ClaimFileMeta[] => {
|
||||
if (!c) return [];
|
||||
|
||||
// If it's already a plain array (runtime from Prisma include), return it
|
||||
const maybeFiles = (c as any).claimFiles;
|
||||
if (!maybeFiles) return [];
|
||||
|
||||
if (Array.isArray(maybeFiles)) {
|
||||
// ensure each item has filename field (best-effort)
|
||||
return maybeFiles.map((f: any) => ({
|
||||
id: f?.id,
|
||||
filename: String(f?.filename ?? ""),
|
||||
mimeType: f?.mimeType ?? f?.mime ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
// Nested-create shape: { create: [...] }
|
||||
if (maybeFiles && Array.isArray(maybeFiles.create)) {
|
||||
return maybeFiles.create.map((f: any) => ({
|
||||
id: f?.id,
|
||||
filename: String(f?.filename ?? ""),
|
||||
mimeType: f?.mimeType ?? f?.mime ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
// No recognized shape -> empty
|
||||
return [];
|
||||
};
|
||||
|
||||
const claimFiles = getClaimFilesArray(claim);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
@@ -204,6 +239,37 @@ export default function ClaimViewModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Claim Files (metadata) */}
|
||||
<div className="pt-4">
|
||||
<h4 className="font-medium text-gray-900 flex items-center space-x-2">
|
||||
<Paperclip className="w-4 h-4 inline-block" />
|
||||
<span>Attached Files</span>
|
||||
</h4>
|
||||
|
||||
{claimFiles.length > 0 ? (
|
||||
<ul className="mt-3 space-y-2">
|
||||
{claimFiles.map((f) => (
|
||||
<li
|
||||
key={f.id ?? f.filename}
|
||||
className="flex items-center justify-between border rounded-md p-3 bg-white"
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
<FileText className="w-5 h-5 text-gray-500 mt-1" />
|
||||
<div>
|
||||
<div className="font-medium">{f.filename}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{f.mimeType || "unknown"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="mt-2 text-gray-500">No files attached.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
|
||||
@@ -121,6 +121,7 @@ model Claim {
|
||||
staff Staff? @relation("ClaimStaff", fields: [staffId], references: [id])
|
||||
|
||||
serviceLines ServiceLine[]
|
||||
claimFiles ClaimFile[]
|
||||
payment Payment?
|
||||
}
|
||||
|
||||
@@ -162,6 +163,15 @@ enum ServiceLineStatus {
|
||||
DENIED
|
||||
}
|
||||
|
||||
model ClaimFile {
|
||||
id Int @id @default(autoincrement())
|
||||
claimId Int
|
||||
filename String
|
||||
mimeType String
|
||||
|
||||
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model InsuranceCredential {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ClaimStatusSchema, ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import {z} from "zod";
|
||||
import {
|
||||
ClaimStatusSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { Staff } from "@repo/db/types";
|
||||
|
||||
@@ -34,7 +37,11 @@ export const ExtendedClaimSchema = (
|
||||
export type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
||||
|
||||
|
||||
export type ClaimFileMeta = {
|
||||
id?: number;
|
||||
filename: string;
|
||||
mimeType?: string | null;
|
||||
};
|
||||
|
||||
//used in claim-form
|
||||
export interface InputServiceLine {
|
||||
@@ -64,4 +71,5 @@ export type ClaimWithServiceLines = Claim & {
|
||||
status: string;
|
||||
}[];
|
||||
staff?: Staff | null;
|
||||
claimFiles?: ClaimFileMeta[] | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user