Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
68d86e08ea
commit
3374695d1b
@@ -0,0 +1,149 @@
|
||||
-- Invoice line items: allow grouping by case + manual editing
|
||||
CREATE TABLE public.invoice_line_items (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
invoice_id uuid NOT NULL REFERENCES public.invoices(id) ON DELETE CASCADE,
|
||||
case_id uuid REFERENCES public.cases(id) ON DELETE SET NULL,
|
||||
kind text NOT NULL DEFAULT 'manual', -- 'time' | 'expense' | 'manual'
|
||||
description text NOT NULL DEFAULT '',
|
||||
work_date date,
|
||||
quantity numeric NOT NULL DEFAULT 1,
|
||||
rate numeric NOT NULL DEFAULT 0,
|
||||
amount numeric NOT NULL DEFAULT 0,
|
||||
time_entry_id uuid REFERENCES public.time_entries(id) ON DELETE SET NULL,
|
||||
expense_id uuid REFERENCES public.expenses(id) ON DELETE SET NULL,
|
||||
user_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
|
||||
sort_order integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_invoice_line_items_invoice ON public.invoice_line_items(invoice_id);
|
||||
CREATE INDEX idx_invoice_line_items_case ON public.invoice_line_items(case_id);
|
||||
|
||||
ALTER TABLE public.invoice_line_items ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "ili_select" ON public.invoice_line_items FOR SELECT TO authenticated
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_line_items.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE POLICY "ili_insert" ON public.invoice_line_items FOR INSERT TO authenticated
|
||||
WITH CHECK (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_line_items.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE POLICY "ili_update" ON public.invoice_line_items FOR UPDATE TO authenticated
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_line_items.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE POLICY "ili_delete" ON public.invoice_line_items FOR DELETE TO authenticated
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_line_items.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE TRIGGER trg_ili_updated BEFORE UPDATE ON public.invoice_line_items
|
||||
FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- Invoice payments
|
||||
CREATE TABLE public.invoice_payments (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
invoice_id uuid NOT NULL REFERENCES public.invoices(id) ON DELETE CASCADE,
|
||||
amount numeric NOT NULL DEFAULT 0,
|
||||
paid_on date NOT NULL DEFAULT CURRENT_DATE,
|
||||
method text,
|
||||
reference text,
|
||||
notes text,
|
||||
created_by uuid REFERENCES public.profiles(id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_invoice_payments_invoice ON public.invoice_payments(invoice_id);
|
||||
|
||||
ALTER TABLE public.invoice_payments ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "ip_select" ON public.invoice_payments FOR SELECT TO authenticated
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_payments.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE POLICY "ip_insert" ON public.invoice_payments FOR INSERT TO authenticated
|
||||
WITH CHECK (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_payments.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
CREATE POLICY "ip_delete" ON public.invoice_payments FOR DELETE TO authenticated
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM public.invoices i
|
||||
WHERE i.id = invoice_payments.invoice_id
|
||||
AND (public.is_admin(auth.uid()) OR i.created_by = auth.uid()
|
||||
OR (i.case_id IS NOT NULL AND public.can_access_case(i.case_id, auth.uid())))
|
||||
));
|
||||
|
||||
-- Trigger: recompute invoice amount_paid / paid_at / status from payments
|
||||
CREATE OR REPLACE FUNCTION public.tg_recompute_invoice_payments()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_invoice_id uuid;
|
||||
v_total numeric;
|
||||
v_paid numeric;
|
||||
v_due date;
|
||||
v_current_status invoice_status;
|
||||
v_new_status invoice_status;
|
||||
v_last_paid timestamptz;
|
||||
BEGIN
|
||||
v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id);
|
||||
|
||||
SELECT total, due_date, status INTO v_total, v_due, v_current_status
|
||||
FROM public.invoices WHERE id = v_invoice_id;
|
||||
|
||||
SELECT COALESCE(SUM(amount), 0), MAX(created_at)
|
||||
INTO v_paid, v_last_paid
|
||||
FROM public.invoice_payments WHERE invoice_id = v_invoice_id;
|
||||
|
||||
v_new_status := v_current_status;
|
||||
IF v_current_status <> 'void' AND v_current_status <> 'draft' THEN
|
||||
IF v_paid >= v_total AND v_total > 0 THEN
|
||||
v_new_status := 'paid';
|
||||
ELSIF v_due IS NOT NULL AND v_due < CURRENT_DATE AND v_paid < v_total THEN
|
||||
v_new_status := 'overdue';
|
||||
ELSIF v_current_status = 'paid' AND v_paid < v_total THEN
|
||||
v_new_status := 'sent';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
UPDATE public.invoices
|
||||
SET amount_paid = v_paid,
|
||||
paid_at = CASE WHEN v_paid >= v_total AND v_total > 0 THEN v_last_paid ELSE NULL END,
|
||||
status = v_new_status,
|
||||
updated_at = now()
|
||||
WHERE id = v_invoice_id;
|
||||
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_invoice_payments_recompute
|
||||
AFTER INSERT OR UPDATE OR DELETE ON public.invoice_payments
|
||||
FOR EACH ROW EXECUTE FUNCTION public.tg_recompute_invoice_payments();
|
||||
Reference in New Issue
Block a user