feat: add Copayment and Adjustment columns to payments table
- Added copayment and adjustment fields (Decimal, default 0) to Payment model in schema and directly to DB via ALTER TABLE - Added PATCH /api/payments/:id/copayment and /adjustment routes - Added inline-editable Copayment and Adjustment columns after MH Paid with same click-to-edit format; Copayment in blue, Adjustment in orange Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -455,6 +455,60 @@ router.patch(
|
||||
}
|
||||
);
|
||||
|
||||
// PATCH /api/payments/:id/copayment
|
||||
router.patch(
|
||||
"/:id/copayment",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||
|
||||
const paymentId = parseIntOrError(req.params.id, "Payment ID");
|
||||
const val = parseFloat(req.body.copayment);
|
||||
if (isNaN(val) || val < 0) {
|
||||
return res.status(400).json({ message: "Invalid copayment value" });
|
||||
}
|
||||
|
||||
const updated = await prisma.payment.update({
|
||||
where: { id: paymentId },
|
||||
data: { copayment: val, updatedById: userId },
|
||||
});
|
||||
|
||||
return res.json({ ...updated, copayment: Number(updated.copayment) });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Failed to update copayment";
|
||||
return res.status(500).json({ message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// PATCH /api/payments/:id/adjustment
|
||||
router.patch(
|
||||
"/:id/adjustment",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||
|
||||
const paymentId = parseIntOrError(req.params.id, "Payment ID");
|
||||
const val = parseFloat(req.body.adjustment);
|
||||
if (isNaN(val) || val < 0) {
|
||||
return res.status(400).json({ message: "Invalid adjustment value" });
|
||||
}
|
||||
|
||||
const updated = await prisma.payment.update({
|
||||
where: { id: paymentId },
|
||||
data: { adjustment: val, updatedById: userId },
|
||||
});
|
||||
|
||||
return res.json({ ...updated, adjustment: Number(updated.adjustment) });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Failed to update adjustment";
|
||||
return res.status(500).json({ message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// PATCH /api/payments/:id/mh-payment-check
|
||||
router.patch(
|
||||
"/:id/mh-payment-check",
|
||||
|
||||
@@ -101,6 +101,10 @@ export default function PaymentsRecentTable({
|
||||
const [isMhChecking, setIsMhChecking] = useState(false);
|
||||
const [editingMhPaidId, setEditingMhPaidId] = useState<number | null>(null);
|
||||
const [editingMhPaidValue, setEditingMhPaidValue] = useState<string>("");
|
||||
const [editingCopaymentId, setEditingCopaymentId] = useState<number | null>(null);
|
||||
const [editingCopaymentValue, setEditingCopaymentValue] = useState<string>("");
|
||||
const [editingAdjustmentId, setEditingAdjustmentId] = useState<number | null>(null);
|
||||
const [editingAdjustmentValue, setEditingAdjustmentValue] = useState<string>("");
|
||||
|
||||
const [isRevertOpen, setIsRevertOpen] = useState(false);
|
||||
const [revertPaymentId, setRevertPaymentId] = useState<number | null>(null);
|
||||
@@ -591,6 +595,8 @@ export default function PaymentsRecentTable({
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Attachments</TableHead>
|
||||
<TableHead>MH Paid</TableHead>
|
||||
<TableHead>Copayment</TableHead>
|
||||
<TableHead>Adjustment</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
<TableHead>Payment ID</TableHead>
|
||||
<TableHead>Claim ID</TableHead>
|
||||
@@ -844,6 +850,98 @@ export default function PaymentsRecentTable({
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
{/* Copayment */}
|
||||
<TableCell>
|
||||
{editingCopaymentId === payment.id ? (
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
autoFocus
|
||||
className="w-24 border border-blue-400 rounded px-1 py-0.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
value={editingCopaymentValue}
|
||||
onChange={(e) => setEditingCopaymentValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.currentTarget.blur();
|
||||
else if (e.key === "Escape") setEditingCopaymentId(null);
|
||||
}}
|
||||
onBlur={async () => {
|
||||
const val = parseFloat(editingCopaymentValue);
|
||||
if (!isNaN(val) && val >= 0) {
|
||||
try {
|
||||
const res = await apiRequest("PATCH", `/api/payments/${payment.id}/copayment`, { copayment: val });
|
||||
if (res.ok) {
|
||||
await queryClient.invalidateQueries({ queryKey: QK_PAYMENTS_RECENT_BASE });
|
||||
} else {
|
||||
toast({ title: "Error", description: "Failed to save copayment.", variant: "destructive" });
|
||||
}
|
||||
} catch {
|
||||
toast({ title: "Error", description: "Failed to save copayment.", variant: "destructive" });
|
||||
}
|
||||
}
|
||||
setEditingCopaymentId(null);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-sm font-medium text-blue-700 cursor-pointer hover:underline hover:text-blue-900"
|
||||
title="Click to edit"
|
||||
onClick={() => {
|
||||
setEditingCopaymentId(payment.id);
|
||||
setEditingCopaymentValue(Number(payment.copayment ?? 0).toFixed(2));
|
||||
}}
|
||||
>
|
||||
${Number(payment.copayment ?? 0).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
{/* Adjustment */}
|
||||
<TableCell>
|
||||
{editingAdjustmentId === payment.id ? (
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
autoFocus
|
||||
className="w-24 border border-blue-400 rounded px-1 py-0.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
value={editingAdjustmentValue}
|
||||
onChange={(e) => setEditingAdjustmentValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.currentTarget.blur();
|
||||
else if (e.key === "Escape") setEditingAdjustmentId(null);
|
||||
}}
|
||||
onBlur={async () => {
|
||||
const val = parseFloat(editingAdjustmentValue);
|
||||
if (!isNaN(val) && val >= 0) {
|
||||
try {
|
||||
const res = await apiRequest("PATCH", `/api/payments/${payment.id}/adjustment`, { adjustment: val });
|
||||
if (res.ok) {
|
||||
await queryClient.invalidateQueries({ queryKey: QK_PAYMENTS_RECENT_BASE });
|
||||
} else {
|
||||
toast({ title: "Error", description: "Failed to save adjustment.", variant: "destructive" });
|
||||
}
|
||||
} catch {
|
||||
toast({ title: "Error", description: "Failed to save adjustment.", variant: "destructive" });
|
||||
}
|
||||
}
|
||||
setEditingAdjustmentId(null);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-sm font-medium text-orange-700 cursor-pointer hover:underline hover:text-orange-900"
|
||||
title="Click to edit"
|
||||
onClick={() => {
|
||||
setEditingAdjustmentId(payment.id);
|
||||
setEditingAdjustmentValue(Number(payment.adjustment ?? 0).toFixed(2));
|
||||
}}
|
||||
>
|
||||
${Number(payment.adjustment ?? 0).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end space-x-2">
|
||||
{allowDelete && (
|
||||
|
||||
Reference in New Issue
Block a user