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:
Gitead
2026-05-15 23:51:39 -04:00
parent 25a20e8a16
commit 7360b1930b
366 changed files with 10822 additions and 388 deletions

View File

@@ -14,8 +14,10 @@ import {
SelectValue,
} from "@/components/ui/select";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
import React, { useState } from "react";
import { ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
import React, { useEffect, useState } from "react";
import { ClaimStatus, ClaimWithServiceLines, NpiProvider } from "@repo/db/types";
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import {
safeParseMissingTeeth,
splitTeeth,
@@ -41,6 +43,28 @@ export default function ClaimEditModal({
const [status, setStatus] = useState<ClaimStatus>(
claim?.status ?? ("PENDING" as ClaimStatus)
);
const [selectedNpiProviderId, setSelectedNpiProviderId] = useState<number | null>(
(claim as any)?.npiProviderId ?? null
);
const { data: npiProviders = [] } = useQuery<NpiProvider[]>({
queryKey: ["/api/npiProviders/"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/npiProviders/");
return res.json();
},
});
// Default to Mary Scannell (or first provider) only when no provider is set
useEffect(() => {
if (!npiProviders.length) return;
if (selectedNpiProviderId !== null) return;
const mary = npiProviders.find((p) =>
p.providerName.toLowerCase().includes("mary scannell")
);
const fallback = mary ?? npiProviders[0];
if (fallback) setSelectedNpiProviderId(fallback.id);
}, [npiProviders]);
if (!claim) return null;
@@ -48,7 +72,9 @@ export default function ClaimEditModal({
const updatedClaim: ClaimWithServiceLines = {
...claim,
status,
};
npiProviderId: selectedNpiProviderId,
npiProvider: npiProviders.find((p) => p.id === selectedNpiProviderId) ?? null,
} as ClaimWithServiceLines;
onSave(updatedClaim);
onOpenChange(false);
@@ -109,6 +135,25 @@ export default function ClaimEditModal({
</SelectContent>
</Select>
</div>
<div>
<span className="text-gray-500">Rendering Provider:</span>
<Select
value={selectedNpiProviderId?.toString() ?? ""}
onValueChange={(val) => setSelectedNpiProviderId(Number(val))}
>
<SelectTrigger className="mt-1 w-full">
<SelectValue placeholder="Select Provider" />
</SelectTrigger>
<SelectContent>
{npiProviders.map((p) => (
<SelectItem key={p.id} value={p.id.toString()}>
{p.npiNumber} {p.providerName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>