From af4c64bba76e4f0746b1db27b55f23e9f5cb3f33 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Wed, 11 Mar 2026 12:40:03 +1100 Subject: [PATCH] feat(splits): save split as rule from split modal - Checkbox in split modal: 'Also save as rule for ' - Creates a rule with apply_split action storing the participant shares - Rules engine now handles apply_split: deletes existing splits and re-applies - Bulk split mode hides the checkbox (rule wouldn't make sense for ad-hoc bulk) --- src/app/api/rules/apply/route.ts | 18 +++++++++++++++ src/app/transactions/page.tsx | 5 ++-- src/components/split-modal.tsx | 39 +++++++++++++++++++++++++++++--- src/lib/hooks.ts | 2 +- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/app/api/rules/apply/route.ts b/src/app/api/rules/apply/route.ts index 1c1fd58..d132cc2 100644 --- a/src/app/api/rules/apply/route.ts +++ b/src/app/api/rules/apply/route.ts @@ -9,10 +9,16 @@ interface Condition { value: string; } +interface SplitEntry { + participant_id: number; + share_percent: number; +} + interface Actions { set_category?: string; add_tag_ids?: number[]; set_merchant?: string; + apply_split?: SplitEntry[]; } interface TxFields { @@ -108,6 +114,18 @@ export async function POST(req: NextRequest) { ); } } + + if (actions.apply_split?.length) { + // Delete existing splits then insert new ones + await queryRaw(`DELETE FROM transaction_splits WHERE transaction_id = $1`, [tx.id]); + for (const s of actions.apply_split) { + await queryRaw( + `INSERT INTO transaction_splits (transaction_id, participant_id, share_percent) + VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`, + [tx.id, s.participant_id, s.share_percent] + ); + } + } } } diff --git a/src/app/transactions/page.tsx b/src/app/transactions/page.tsx index 64cf66c..8602798 100644 --- a/src/app/transactions/page.tsx +++ b/src/app/transactions/page.tsx @@ -232,7 +232,7 @@ function TransactionsContent() { const [selected, setSelected] = useState>(new Set()); const [bulkCategory, setBulkCategory] = useState(""); const [bulkTagId, setBulkTagId] = useState(""); - const [splitModal, setSplitModal] = useState<{ transactionId?: number; transactionIds?: number[]; amount?: number; description: string } | null>(null); + const [splitModal, setSplitModal] = useState<{ transactionId?: number; transactionIds?: number[]; amount?: number; description: string; merchant?: string } | null>(null); const [rulePrompt, setRulePrompt] = useState<{ tx: { id: number; effective_merchant: string; description: string; bank_name: string }; field: "category" | "merchant"; @@ -543,7 +543,7 @@ function TransactionsContent() {