feat(finance): Phase 5 — Rules Engine
Add rules engine with CRUD API, condition/action evaluation, and apply-all endpoint. UI: rule builder form with field/operator/value conditions, tag multi-select, apply button with result stats.
This commit is contained in:
@@ -352,3 +352,82 @@ export function useCreateParticipant() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --- Rules ---
|
||||
|
||||
export interface RuleRow {
|
||||
id: number;
|
||||
name: string;
|
||||
conditions: { field: string; operator: string; value: string }[];
|
||||
actions: { set_category?: string; add_tag_ids?: number[]; set_merchant?: string };
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function useRules() {
|
||||
return useQuery<RuleRow[]>({
|
||||
queryKey: ["rules"],
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/rules");
|
||||
return res.json();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateRule() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (data: Omit<RuleRow, "id" | "created_at">) => {
|
||||
const res = await fetch("/api/rules", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed to create rule");
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["rules"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRule() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, ...data }: Partial<RuleRow> & { id: number }) => {
|
||||
const res = await fetch(`/api/rules/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed to update rule");
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["rules"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRule() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (id: number) => {
|
||||
await fetch(`/api/rules/${id}`, { method: "DELETE" });
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["rules"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useApplyRules() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const res = await fetch("/api/rules/apply", { method: "POST" });
|
||||
if (!res.ok) throw new Error("Failed to apply rules");
|
||||
return res.json() as Promise<{ matched: number; transactions_affected: number }>;
|
||||
},
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["transactions"] });
|
||||
qc.invalidateQueries({ queryKey: ["rules"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user