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:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user