From b6659ef66eeef03f75adb545e19887820977dcbc Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Thu, 21 May 2026 13:46:42 +0000 Subject: [PATCH] feat(importacao): melhora revisao de extratos --- .../transactions/actions/import-action.ts | 42 ++++-- .../components/import/global-fields.tsx | 20 +-- .../components/import/import-page.tsx | 127 ++++++++++++++---- .../components/import/import-summary.tsx | 17 ++- .../components/import/review-table.tsx | 50 ++++++- src/shared/lib/import/xls-parser.ts | 16 +-- 6 files changed, 208 insertions(+), 64 deletions(-) diff --git a/src/features/transactions/actions/import-action.ts b/src/features/transactions/actions/import-action.ts index 97b4737..e75202b 100644 --- a/src/features/transactions/actions/import-action.ts +++ b/src/features/transactions/actions/import-action.ts @@ -4,9 +4,10 @@ import { and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; import { transactions } from "@/db/schema"; import { + fetchOwnedCategoryIds, + fetchOwnedPayerIds, validateCartaoOwnership, validateContaOwnership, - validatePayerOwnership, } from "@/features/transactions/actions/core"; import { revalidateForEntity } from "@/shared/lib/actions/helpers"; import { getUserId } from "@/shared/lib/auth/server"; @@ -21,6 +22,7 @@ const importRowSchema = z.object({ description: z.string().min(1, "Descrição obrigatória."), transactionType: z.enum(["income", "expense"]), categoryId: uuidSchema("Category").nullable().optional(), + payerId: uuidSchema("Payer").nullable().optional(), }); const importSchema = z.object({ @@ -76,14 +78,34 @@ export async function importTransactionsAction( const { rows, payerId, accountId, cardId, paymentMethod, invoicePeriod } = parsed.data; - // Valida ownership - const [payerOk, accountOk, cardOk] = await Promise.all([ - validatePayerOwnership(userId, payerId), - validateContaOwnership(userId, accountId), - validateCartaoOwnership(userId, cardId), - ]); + const payerIdsByRow = rows.map((row) => row.payerId ?? payerId ?? null); + + if (payerIdsByRow.some((id) => !id)) { + return { success: false, error: "Pessoa obrigatória." }; + } + + // Valida ownership + const [ownedPayerIds, ownedCategoryIds, accountOk, cardOk] = + await Promise.all([ + fetchOwnedPayerIds(userId, payerIdsByRow), + fetchOwnedCategoryIds( + userId, + rows.map((row) => row.categoryId), + ), + validateContaOwnership(userId, accountId), + validateCartaoOwnership(userId, cardId), + ]); + + if (payerIdsByRow.some((id) => id && !ownedPayerIds.has(id))) { + return { success: false, error: "Pessoa não encontrada." }; + } + + if ( + rows.some((row) => row.categoryId && !ownedCategoryIds.has(row.categoryId)) + ) { + return { success: false, error: "Categoria não encontrada." }; + } - if (!payerOk) return { success: false, error: "Pessoa não encontrada." }; if (!accountOk) return { success: false, error: "Conta não encontrada." }; if (!cardOk) return { success: false, error: "Cartão não encontrado." }; @@ -96,7 +118,7 @@ export async function importTransactionsAction( // Cartão de crédito: fatura pode ainda não ter sido paga const isSettled = paymentMethod !== "Cartão de crédito"; - const records = rows.map((row) => { + const records = rows.map((row, index) => { const purchaseDate = parseLocalDateString(row.date); const period = invoicePeriod ?? @@ -115,7 +137,7 @@ export async function importTransactionsAction( period, isSettled, userId, - payerId: payerId ?? null, + payerId: payerIdsByRow[index], accountId: accountId ?? null, cardId: cardId ?? null, categoryId: row.categoryId ?? null, diff --git a/src/features/transactions/components/import/global-fields.tsx b/src/features/transactions/components/import/global-fields.tsx index ead1a13..8e25214 100644 --- a/src/features/transactions/components/import/global-fields.tsx +++ b/src/features/transactions/components/import/global-fields.tsx @@ -74,16 +74,16 @@ export function GlobalFields({ return (

- Aplicado a todos os lançamentos importados. + Aplicado aos lançamentos selecionados.

-
-
+
+
-
+
-
+
onPayerChange(index, v || null)} + > + + + + + {payerOptions.map((opt) => ( + + + + ))} + + +