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
@@ -1110,6 +1110,147 @@ export type Database = {
|
||||
},
|
||||
]
|
||||
}
|
||||
invoice_line_items: {
|
||||
Row: {
|
||||
amount: number
|
||||
case_id: string | null
|
||||
created_at: string
|
||||
description: string
|
||||
expense_id: string | null
|
||||
id: string
|
||||
invoice_id: string
|
||||
kind: string
|
||||
quantity: number
|
||||
rate: number
|
||||
sort_order: number
|
||||
time_entry_id: string | null
|
||||
updated_at: string
|
||||
user_id: string | null
|
||||
work_date: string | null
|
||||
}
|
||||
Insert: {
|
||||
amount?: number
|
||||
case_id?: string | null
|
||||
created_at?: string
|
||||
description?: string
|
||||
expense_id?: string | null
|
||||
id?: string
|
||||
invoice_id: string
|
||||
kind?: string
|
||||
quantity?: number
|
||||
rate?: number
|
||||
sort_order?: number
|
||||
time_entry_id?: string | null
|
||||
updated_at?: string
|
||||
user_id?: string | null
|
||||
work_date?: string | null
|
||||
}
|
||||
Update: {
|
||||
amount?: number
|
||||
case_id?: string | null
|
||||
created_at?: string
|
||||
description?: string
|
||||
expense_id?: string | null
|
||||
id?: string
|
||||
invoice_id?: string
|
||||
kind?: string
|
||||
quantity?: number
|
||||
rate?: number
|
||||
sort_order?: number
|
||||
time_entry_id?: string | null
|
||||
updated_at?: string
|
||||
user_id?: string | null
|
||||
work_date?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "invoice_line_items_case_id_fkey"
|
||||
columns: ["case_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "cases"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invoice_line_items_expense_id_fkey"
|
||||
columns: ["expense_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "expenses"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invoice_line_items_invoice_id_fkey"
|
||||
columns: ["invoice_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "invoices"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invoice_line_items_time_entry_id_fkey"
|
||||
columns: ["time_entry_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "time_entries"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invoice_line_items_user_id_fkey"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
invoice_payments: {
|
||||
Row: {
|
||||
amount: number
|
||||
created_at: string
|
||||
created_by: string | null
|
||||
id: string
|
||||
invoice_id: string
|
||||
method: string | null
|
||||
notes: string | null
|
||||
paid_on: string
|
||||
reference: string | null
|
||||
}
|
||||
Insert: {
|
||||
amount?: number
|
||||
created_at?: string
|
||||
created_by?: string | null
|
||||
id?: string
|
||||
invoice_id: string
|
||||
method?: string | null
|
||||
notes?: string | null
|
||||
paid_on?: string
|
||||
reference?: string | null
|
||||
}
|
||||
Update: {
|
||||
amount?: number
|
||||
created_at?: string
|
||||
created_by?: string | null
|
||||
id?: string
|
||||
invoice_id?: string
|
||||
method?: string | null
|
||||
notes?: string | null
|
||||
paid_on?: string
|
||||
reference?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "invoice_payments_created_by_fkey"
|
||||
columns: ["created_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invoice_payments_invoice_id_fkey"
|
||||
columns: ["invoice_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "invoices"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
invoices: {
|
||||
Row: {
|
||||
amount_paid: number
|
||||
|
||||
@@ -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