feat: add provider column, commission tracking, and report provider filter
- Claims & Payments: save npiProviderId when submitting MH claim; sync between claim and payment on update - Claims table: add Provider column showing rendering provider name - Payments table: add Provider column + purple Commissioned badge on status - Claim edit modal: add Rendering Provider dropdown (defaults to Mary Scannell) - Payment edit modal: add Rendering Provider dropdown + Commissioned metadata display - Reports page: add Provider filter dropdown (dynamic from NPI providers settings) - Reports page: remove Collections by Doctor report type and Select Doctor dropdown - Commission section: new section in reports page with date range + provider filter, shows eligible paid claims/payments per provider, multi-select checkboxes, Pay Commission modal with print + save, marks payments as commissioned so they are excluded from future cycles - DB: add CommissionBatch and CommissionBatchItem tables; backfill Payment.npiProviderId from linked claims - Backend: PATCH /api/payments/:id/provider syncs to linked claim; PUT /api/claims/:id syncs to linked payment; new /api/commissions routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,14 @@ import {
|
||||
import { Calendar } from "lucide-react";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
import { DateInput } from "@/components/ui/dateInput";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import { NpiProvider } from "@repo/db/types";
|
||||
|
||||
type ReportType =
|
||||
| "patients_with_balance"
|
||||
| "patients_no_balance"
|
||||
| "monthly_collections"
|
||||
| "collections_by_doctor"
|
||||
| "procedure_codes_by_doctor"
|
||||
| "payment_methods"
|
||||
| "insurance_vs_patient_payments"
|
||||
@@ -29,34 +31,37 @@ export default function ReportConfig({
|
||||
setEndDate,
|
||||
selectedReportType,
|
||||
setSelectedReportType,
|
||||
npiProviderId,
|
||||
setNpiProviderId,
|
||||
}: {
|
||||
startDate: string; // "" or "YYYY-MM-DD"
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
setStartDate: (s: string) => void;
|
||||
setEndDate: (s: string) => void;
|
||||
selectedReportType: ReportType;
|
||||
setSelectedReportType: (r: ReportType) => void;
|
||||
npiProviderId: number | null;
|
||||
setNpiProviderId: (id: number | null) => void;
|
||||
}) {
|
||||
// Convert incoming string -> Date | null using your parseLocalDate utility.
|
||||
// parseLocalDate can throw for invalid strings, so guard with try/catch.
|
||||
let startDateObj: Date | null = null;
|
||||
if (startDate) {
|
||||
try {
|
||||
startDateObj = parseLocalDate(startDate);
|
||||
} catch {
|
||||
startDateObj = null;
|
||||
}
|
||||
try { startDateObj = parseLocalDate(startDate); } catch { startDateObj = null; }
|
||||
}
|
||||
|
||||
let endDateObj: Date | null = null;
|
||||
if (endDate) {
|
||||
try {
|
||||
endDateObj = parseLocalDate(endDate);
|
||||
} catch {
|
||||
endDateObj = null;
|
||||
}
|
||||
try { endDateObj = parseLocalDate(endDate); } catch { endDateObj = null; }
|
||||
}
|
||||
|
||||
const { data: npiProviders = [] } = useQuery<NpiProvider[]>({
|
||||
queryKey: ["/api/npiProviders/"],
|
||||
queryFn: async () => {
|
||||
const res = await apiRequest("GET", "/api/npiProviders/");
|
||||
return res.json();
|
||||
},
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -67,17 +72,15 @@ export default function ReportConfig({
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
<div className="text-sm text-gray-500">
|
||||
Choose the report type and date range.
|
||||
Choose the report type, date range, and provider.
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<DateInput
|
||||
label="Start Date"
|
||||
value={startDateObj}
|
||||
onChange={(d) => {
|
||||
setStartDate(d ? formatLocalDate(d) : "");
|
||||
}}
|
||||
onChange={(d) => setStartDate(d ? formatLocalDate(d) : "")}
|
||||
disableFuture
|
||||
/>
|
||||
</div>
|
||||
@@ -86,13 +89,33 @@ export default function ReportConfig({
|
||||
<DateInput
|
||||
label="End Date"
|
||||
value={endDateObj}
|
||||
onChange={(d) => {
|
||||
setEndDate(d ? formatLocalDate(d) : "");
|
||||
}}
|
||||
onChange={(d) => setEndDate(d ? formatLocalDate(d) : "")}
|
||||
disableFuture
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Provider</Label>
|
||||
<Select
|
||||
value={npiProviderId?.toString() ?? "all"}
|
||||
onValueChange={(v) =>
|
||||
setNpiProviderId(v === "all" ? null : Number(v))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="All Providers" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Providers</SelectItem>
|
||||
{npiProviders.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id.toString()}>
|
||||
{p.providerName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="report-type">Report Type</Label>
|
||||
<Select
|
||||
@@ -106,9 +129,6 @@ export default function ReportConfig({
|
||||
<SelectItem value="patients_with_balance">
|
||||
Patients with Outstanding Balance
|
||||
</SelectItem>
|
||||
<SelectItem value="collections_by_doctor">
|
||||
Collections by Doctor
|
||||
</SelectItem>
|
||||
<SelectItem value="patients_no_balance">
|
||||
Patients with Zero Balance
|
||||
</SelectItem>
|
||||
|
||||
Reference in New Issue
Block a user