Linked ledger plans to cases

X-Lovable-Edit-ID: edt-ba9cc0b8-d732-4be2-a037-627c460c4775
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-26 08:53:11 +00:00
co-authored by renee-png
3 changed files with 75 additions and 4 deletions
@@ -65,7 +65,13 @@ function addInterval(dateStr: string, frequency: Frequency, n: number) {
return d.toISOString().slice(0, 10);
}
export function PaymentPlansPanel({ collectionId }: { collectionId: string }) {
export function PaymentPlansPanel({
collectionId,
caseId,
}: {
collectionId: string;
caseId?: string | null;
}) {
const { user } = useAuth();
const [plans, setPlans] = useState<Plan[]>([]);
const [installments, setInstallments] = useState<Record<string, Installment[]>>({});
@@ -119,6 +125,25 @@ export function PaymentPlansPanel({ collectionId }: { collectionId: string }) {
else load();
};
const updatePaidAmount = async (id: string, value: string) => {
const num = value === "" ? null : parseFloat(value) || 0;
setInstallments((prev) => {
const next: Record<string, Installment[]> = {};
Object.entries(prev).forEach(([k, list]) => {
next[k] = list.map((x) => (x.id === id ? { ...x, paid_amount: num } : x));
});
return next;
});
const { error } = await supabase
.from("payment_plan_installments")
.update({ paid_amount: num })
.eq("id", id);
if (error) {
toast.error("Could not save", { description: error.message });
load();
}
};
const updatePlanStatus = async (planId: string, status: PlanStatus) => {
const { error } = await supabase
.from("payment_plans")
@@ -220,8 +245,21 @@ export function PaymentPlansPanel({ collectionId }: { collectionId: string }) {
{overdue && " · overdue"}
</div>
</div>
<div className="text-sm font-medium tabular-nums">
{formatCurrency(i.amount)}
<div className="flex items-center gap-2">
{i.paid ? (
<Input
type="number"
step="0.01"
value={i.paid_amount ?? i.amount}
onChange={(e) => updatePaidAmount(i.id, e.target.value)}
className="h-7 w-[100px] text-sm text-right tabular-nums"
title="Amount paid"
/>
) : (
<div className="text-sm font-medium tabular-nums">
{formatCurrency(i.amount)}
</div>
)}
</div>
</div>
);
@@ -243,6 +281,7 @@ export function PaymentPlansPanel({ collectionId }: { collectionId: string }) {
open={createOpen}
onOpenChange={setCreateOpen}
collectionId={collectionId}
caseId={caseId ?? null}
userId={user?.id}
onSaved={load}
/>
@@ -264,12 +303,14 @@ function CreatePlanDialog({
open,
onOpenChange,
collectionId,
caseId,
userId,
onSaved,
}: {
open: boolean;
onOpenChange: (v: boolean) => void;
collectionId: string;
caseId?: string | null;
userId?: string;
onSaved: () => void;
}) {
@@ -307,6 +348,7 @@ function CreatePlanDialog({
.from("payment_plans")
.insert({
collection_id: collectionId,
case_id: caseId ?? null,
name: name || null,
total_amount: t,
down_payment: parseFloat(down) || 0,
+1 -1
View File
@@ -598,7 +598,7 @@ function CollectionDetailRoute() {
</Card>
{/* Payment plans */}
<PaymentPlansPanel collectionId={collection.id} />
<PaymentPlansPanel collectionId={collection.id} caseId={collection.case_id ?? null} />
{/* Ledger */}
<CollectionDetail
@@ -0,0 +1,29 @@
-- Backfill case_id on payment_plans created via collections so they show up
-- on the case Payment Plans tab.
UPDATE public.payment_plans pp
SET case_id = c.case_id
FROM public.collections c
WHERE pp.collection_id = c.id
AND pp.case_id IS NULL
AND c.case_id IS NOT NULL;
-- Trigger to auto-populate case_id on insert/update when collection_id is set
-- but case_id is missing.
CREATE OR REPLACE FUNCTION public.payment_plans_fill_case_id()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF NEW.case_id IS NULL AND NEW.collection_id IS NOT NULL THEN
SELECT case_id INTO NEW.case_id FROM public.collections WHERE id = NEW.collection_id;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS payment_plans_fill_case_id_trg ON public.payment_plans;
CREATE TRIGGER payment_plans_fill_case_id_trg
BEFORE INSERT OR UPDATE ON public.payment_plans
FOR EACH ROW EXECUTE FUNCTION public.payment_plans_fill_case_id();