feat: MassHealth PDF import auto-pays full balance + patient name fix
- PDF import now marks payments as PAID when MassHealth patient's mhPaidAmount >= totalBilled (no patient balance) - Newly created patients from MH vouchers get insuranceProvider = 'MassHealth' - Existing patients with blank insuranceProvider get it filled on import - Fix: update patient name from PDF if existing record has empty name - Various frontend/selenium/route updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,12 +31,21 @@ import { apiRequest } from "@/lib/queryClient";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import PaymentEditModal from "@/components/payments/payment-edit-modal";
|
||||
|
||||
function formatDateInput(raw: string): string {
|
||||
const digits = raw.replace(/\D/g, "").slice(0, 8);
|
||||
if (digits.length <= 2) return digits;
|
||||
if (digits.length <= 4) return `${digits.slice(0, 2)}/${digits.slice(2)}`;
|
||||
return `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4)}`;
|
||||
}
|
||||
|
||||
export default function PaymentsPage() {
|
||||
const [paymentPeriod, setPaymentPeriod] = useState<string>("all-time");
|
||||
|
||||
// Check Payments Online date range
|
||||
const [mhFromDate, setMhFromDate] = useState<Date | undefined>(undefined);
|
||||
const [mhToDate, setMhToDate] = useState<Date | undefined>(undefined);
|
||||
const [mhFromText, setMhFromText] = useState("");
|
||||
const [mhToText, setMhToText] = useState("");
|
||||
const [fromCalendarOpen, setFromCalendarOpen] = useState(false);
|
||||
const [toCalendarOpen, setToCalendarOpen] = useState(false);
|
||||
|
||||
@@ -229,7 +238,7 @@ export default function PaymentsPage() {
|
||||
{/* Check Payments Online */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Check Payments Online</CardTitle>
|
||||
<CardTitle>Check MH Payments</CardTitle>
|
||||
<CardDescription>
|
||||
Select a date range and check MH payment status online
|
||||
</CardDescription>
|
||||
@@ -239,69 +248,115 @@ export default function PaymentsPage() {
|
||||
{/* From date picker */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-600 whitespace-nowrap">From:</span>
|
||||
<Popover open={fromCalendarOpen} onOpenChange={setFromCalendarOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-[160px] justify-start text-left font-normal"
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4 text-gray-400" />
|
||||
{mhFromDate
|
||||
? mhFromDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
|
||||
: <span className="text-muted-foreground">Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={mhFromDate}
|
||||
onSelect={(d) => {
|
||||
setMhFromDate(d);
|
||||
setFromCalendarOpen(false);
|
||||
}}
|
||||
onClose={() => setFromCalendarOpen(false)}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="MM/DD/YYYY"
|
||||
className="border rounded-md px-2 py-1.5 text-sm w-[110px] focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
value={mhFromText}
|
||||
onChange={(e) => {
|
||||
const formatted = formatDateInput(e.target.value);
|
||||
setMhFromText(formatted);
|
||||
if (formatted === "") { setMhFromDate(undefined); return; }
|
||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(formatted)) {
|
||||
const parsed = new Date(formatted);
|
||||
if (!isNaN(parsed.getTime())) setMhFromDate(parsed);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Popover open={fromCalendarOpen} onOpenChange={setFromCalendarOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" size="icon" className="h-8 w-8 shrink-0">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={mhFromDate}
|
||||
onSelect={(d) => {
|
||||
setMhFromDate(d);
|
||||
setMhFromText(d ? d.toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" }) : "");
|
||||
setFromCalendarOpen(false);
|
||||
}}
|
||||
onClose={() => setFromCalendarOpen(false)}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* To date picker */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-600 whitespace-nowrap">To:</span>
|
||||
<Popover open={toCalendarOpen} onOpenChange={setToCalendarOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-[160px] justify-start text-left font-normal"
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4 text-gray-400" />
|
||||
{mhToDate
|
||||
? mhToDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
|
||||
: <span className="text-muted-foreground">Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={mhToDate}
|
||||
onSelect={(d) => {
|
||||
setMhToDate(d);
|
||||
setToCalendarOpen(false);
|
||||
}}
|
||||
onClose={() => setToCalendarOpen(false)}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="MM/DD/YYYY"
|
||||
className="border rounded-md px-2 py-1.5 text-sm w-[110px] focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
value={mhToText}
|
||||
onChange={(e) => {
|
||||
const formatted = formatDateInput(e.target.value);
|
||||
setMhToText(formatted);
|
||||
if (formatted === "") { setMhToDate(undefined); return; }
|
||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(formatted)) {
|
||||
const parsed = new Date(formatted);
|
||||
if (!isNaN(parsed.getTime())) setMhToDate(parsed);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Popover open={toCalendarOpen} onOpenChange={setToCalendarOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" size="icon" className="h-8 w-8 shrink-0">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={mhToDate}
|
||||
onSelect={(d) => {
|
||||
setMhToDate(d);
|
||||
setMhToText(d ? d.toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" }) : "");
|
||||
setToCalendarOpen(false);
|
||||
}}
|
||||
onClose={() => setToCalendarOpen(false)}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Check All MH Payment button */}
|
||||
{/* MH Batch Payment Check button */}
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
// Logic to be defined later
|
||||
onClick={async () => {
|
||||
if (!mhFromDate || !mhToDate) {
|
||||
toast({ title: "Please select both From and To dates", variant: "destructive" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await apiRequest("POST", "/api/payments/mh-batch-payment-check", {
|
||||
fromDate: mhFromDate.toISOString().split("T")[0],
|
||||
toDate: mhToDate.toISOString().split("T")[0],
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
toast({ title: "MH Batch Payment Check Failed", description: err.message, variant: "destructive" });
|
||||
} else {
|
||||
const result = await res.json();
|
||||
if (result.noResults) {
|
||||
toast({ title: "No Results", description: result.message, variant: "destructive" });
|
||||
} else {
|
||||
toast({ title: "MH Batch Payment Check", description: result.importSummary ?? result.message ?? "Done" });
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast({ title: "Error", description: e.message, variant: "destructive" });
|
||||
}
|
||||
}}
|
||||
>
|
||||
Check All MH Payment
|
||||
Go
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user