diff --git a/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx b/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx index dbab7b2..096bb68 100644 --- a/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx +++ b/src/app/(dashboard)/accounts/[accountId]/statement/page.tsx @@ -43,6 +43,15 @@ type PageProps = { const capitalize = (value: string) => value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value; +const resolveDefaultPaymentMethod = ( + accountType: string | null | undefined, +) => { + if (accountType === "Dinheiro") return "Dinheiro"; + if (accountType === "Pré-Pago | VR/VA") return "Pré-Pago | VR/VA"; + + return "Pix"; +}; + export default async function Page({ params, searchParams }: PageProps) { await connection(); const { accountId } = await params; @@ -197,7 +206,11 @@ export default async function Page({ params, searchParams }: PageProps) { accountId: account.id, settledOnly: true, }} - allowCreate={false} + allowCreate + defaultAccountId={account.id} + defaultPaymentMethod={resolveDefaultPaymentMethod( + account.accountType, + )} noteAsColumn={userPreferences?.statementNoteAsColumn ?? false} columnOrder={userPreferences?.transactionsColumnOrder ?? null} attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50} diff --git a/src/features/dashboard/components/installment-analysis/installment-analysis-page.tsx b/src/features/dashboard/components/installment-analysis/installment-analysis-page.tsx index 2a5fceb..c9eee5d 100644 --- a/src/features/dashboard/components/installment-analysis/installment-analysis-page.tsx +++ b/src/features/dashboard/components/installment-analysis/installment-analysis-page.tsx @@ -130,7 +130,7 @@ export function InstallmentAnalysisPage({ return (
{/* Card de resumo principal */} - +

Se você pagar tudo que está selecionado: diff --git a/src/features/dashboard/components/installment-analysis/installment-group-card.tsx b/src/features/dashboard/components/installment-analysis/installment-group-card.tsx index c95003d..6af1a46 100644 --- a/src/features/dashboard/components/installment-analysis/installment-group-card.tsx +++ b/src/features/dashboard/components/installment-analysis/installment-group-card.tsx @@ -64,8 +64,8 @@ export function InstallmentGroupCard({ const hasSelection = selectedInstallments.size > 0; const progress = - group.totalInstallments > 0 - ? (group.paidInstallments / group.totalInstallments) * 100 + group.trackedInstallments > 0 + ? (group.paidInstallments / group.trackedInstallments) * 100 : 0; const selectedAmount = group.pendingInstallments @@ -83,6 +83,10 @@ export function InstallmentGroupCard({ ); const cardLogoSrc = resolveLogoSrc(group.cartaoLogo); const cardName = group.cartaoName ?? "Compra parcelada"; + const untrackedLabel = + group.untrackedInstallments === 1 + ? "1 parcela anterior fora do acompanhamento" + : `${group.untrackedInstallments} parcelas anteriores fora do acompanhamento`; return ( <> @@ -153,7 +157,7 @@ export function InstallmentGroupCard({

- Valor total + Valor acompanhado

- {group.paidInstallments} de {group.totalInstallments} parcelas - pagas + {group.paidInstallments} de {group.trackedInstallments}{" "} + parcelas acompanhadas pagas
{unpaidCount > 0 && ( @@ -198,6 +202,9 @@ export function InstallmentGroupCard({ className="h-2.5 bg-muted" indicatorClassName="bg-success" /> + {group.untrackedInstallments > 0 && ( +

{untrackedLabel}

+ )}
{/* Valor selecionado */} diff --git a/src/features/dashboard/expenses/installment-analysis-queries.ts b/src/features/dashboard/expenses/installment-analysis-queries.ts index d7f1138..1d9ae14 100644 --- a/src/features/dashboard/expenses/installment-analysis-queries.ts +++ b/src/features/dashboard/expenses/installment-analysis-queries.ts @@ -51,6 +51,9 @@ export type InstallmentGroup = { cartaoDueDay: string | null; cartaoLogo: string | null; totalInstallments: number; + trackedStartInstallment: number; + trackedInstallments: number; + untrackedInstallments: number; paidInstallments: number; pendingInstallments: InstallmentDetail[]; totalPendingAmount: number; @@ -153,6 +156,12 @@ export async function fetchInstallmentAnalysis( cartaoDueDay: row.cartaoDueDay, cartaoLogo: row.cartaoLogo, totalInstallments: row.installmentCount ?? 0, + trackedStartInstallment: installmentDetail.currentInstallment, + trackedInstallments: 1, + untrackedInstallments: Math.max( + 0, + installmentDetail.currentInstallment - 1, + ), paidInstallments: 0, pendingInstallments: [installmentDetail], totalPendingAmount: amount, @@ -168,7 +177,13 @@ export async function fetchInstallmentAnalysis( const paidCount = group.pendingInstallments.filter( (i) => i.isSettled, ).length; + const trackedStartInstallment = Math.min( + ...group.pendingInstallments.map((i) => i.currentInstallment), + ); group.paidInstallments = paidCount; + group.trackedStartInstallment = trackedStartInstallment; + group.trackedInstallments = group.pendingInstallments.length; + group.untrackedInstallments = Math.max(0, trackedStartInstallment - 1); return group; }) // Filtrar apenas séries que têm pelo menos uma parcela em aberto (não paga) diff --git a/src/features/transactions/actions/bulk-actions.ts b/src/features/transactions/actions/bulk-actions.ts index 0be31e8..be35213 100644 --- a/src/features/transactions/actions/bulk-actions.ts +++ b/src/features/transactions/actions/bulk-actions.ts @@ -7,6 +7,7 @@ import { TRANSACTION_CONDITIONS, TRANSACTION_TYPES, } from "@/features/transactions/lib/constants"; +import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants"; import { handleActionError } from "@/shared/lib/actions/helpers"; import { getUser } from "@/shared/lib/auth/server"; import { db } from "@/shared/lib/db"; @@ -30,6 +31,7 @@ import { fetchOwnedPayerIds, formatPaidInvoicePeriods, getPaidInvoicePeriods, + isInitialBalanceTransaction, type MassAddInput, massAddSchema, resolvePeriod, @@ -47,6 +49,19 @@ const getPeriodOffset = (basePeriod: string, targetPeriod: string) => { return (target.year - base.year) * 12 + (target.month - base.month); }; +type ProtectedTransactionCandidate = { + note: string | null; + transactionType: string | null; + condition: string | null; + paymentMethod: string | null; +}; + +const isProtectedTransaction = ( + record: ProtectedTransactionCandidate, +): boolean => + Boolean(record.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) || + isInitialBalanceTransaction(record); + export async function deleteTransactionBulkAction( input: DeleteBulkInput, ): Promise { @@ -61,6 +76,9 @@ export async function deleteTransactionBulkAction( seriesId: true, period: true, condition: true, + transactionType: true, + paymentMethod: true, + note: true, }, where: and( eq(transactions.id, data.id), @@ -79,6 +97,13 @@ export async function deleteTransactionBulkAction( }; } + if (isProtectedTransaction(existing)) { + return { + success: false, + error: "Lançamentos protegidos não podem ser removidos em massa.", + }; + } + let scopeFilter: ReturnType; let successMessage: string; @@ -171,6 +196,7 @@ export async function updateTransactionBulkAction( purchaseDate: true, payerId: true, cardId: true, + note: true, }, where: and( eq(transactions.id, data.id), @@ -189,6 +215,13 @@ export async function updateTransactionBulkAction( }; } + if (isProtectedTransaction(existing)) { + return { + success: false, + error: "Lançamentos protegidos não podem ser atualizados em massa.", + }; + } + const baseUpdatePayload: Record = { name: data.name, categoryId: data.categoryId ?? null, @@ -753,6 +786,13 @@ export async function deleteMultipleTransactionsAction( return { success: false, error: "Nenhum lançamento encontrado." }; } + if (existing.some(isProtectedTransaction)) { + return { + success: false, + error: "Lançamentos protegidos não podem ser removidos em massa.", + }; + } + const linkedAttachments = await db .select({ id: attachments.id, fileKey: attachments.fileKey }) .from(transactionAttachments) diff --git a/src/features/transactions/actions/core.ts b/src/features/transactions/actions/core.ts index 65d1b79..9f6feb9 100644 --- a/src/features/transactions/actions/core.ts +++ b/src/features/transactions/actions/core.ts @@ -335,6 +335,12 @@ const baseFields = z.object({ .min(1, "Selecione uma quantidade válida.") .max(60, "Selecione uma quantidade válida.") .optional(), + startInstallment: z.coerce + .number() + .int() + .min(1, "Selecione uma parcela válida.") + .max(60, "Selecione uma parcela válida.") + .optional(), recurrenceCount: z.coerce .number() .int() @@ -415,6 +421,15 @@ const refineLancamento = ( path: ["installmentCount"], message: "Selecione pelo menos duas parcelas.", }); + } else if ( + data.startInstallment && + data.startInstallment > data.installmentCount + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["startInstallment"], + message: "A parcela inicial não pode ser maior que o total.", + }); } } @@ -651,24 +666,27 @@ export const buildTransactionRecords = ({ if (data.condition === "Parcelado") { const installmentTotal = data.installmentCount ?? 0; + const startInstallment = data.startInstallment ?? 1; const amountsByShare = shares.map((share) => splitAmount(share.amountCents, installmentTotal), ); for ( - let installment = 0; - installment < installmentTotal; - installment += 1 + let index = 0; + index <= installmentTotal - startInstallment; + index += 1 ) { - const installmentPeriod = addMonthsToPeriod(period, installment); + const currentInstallment = startInstallment + index; + const installmentPeriod = addMonthsToPeriod(period, index); const installmentDueDate = dueDate - ? addMonthsToDate(dueDate, installment) + ? addMonthsToDate(dueDate, index) : null; const splitGroupId = cycleSplitGroupId(); shares.forEach((share, shareIndex) => { - const amountCents = amountsByShare[shareIndex]?.[installment] ?? 0; - const settled = resolveSettledValue(installment); + const amountCents = + amountsByShare[shareIndex]?.[currentInstallment - 1] ?? 0; + const settled = resolveSettledValue(index); records.push({ ...basePayload, amount: centsToDecimalString(amountCents * amountSign), @@ -677,7 +695,7 @@ export const buildTransactionRecords = ({ period: installmentPeriod, isSettled: settled, installmentCount: installmentTotal, - currentInstallment: installment + 1, + currentInstallment, recurrenceCount: null, dueDate: installmentDueDate, splitGroupId, diff --git a/src/features/transactions/actions/single-actions.ts b/src/features/transactions/actions/single-actions.ts index b875778..5c2bc7f 100644 --- a/src/features/transactions/actions/single-actions.ts +++ b/src/features/transactions/actions/single-actions.ts @@ -8,6 +8,7 @@ import { transactionAttachments, transactions, } from "@/db/schema"; +import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants"; import { handleActionError } from "@/shared/lib/actions/helpers"; import { getUser } from "@/shared/lib/auth/server"; import { db } from "@/shared/lib/db"; @@ -230,13 +231,6 @@ export async function updateTransactionAction( eq(transactions.id, data.id), eq(transactions.userId, user.id), ), - with: { - category: { - columns: { - name: true, - }, - }, - }, })) as | { id: string; @@ -248,7 +242,6 @@ export async function updateTransactionAction( accountId: string | null; cardId: string | null; categoryId: string | null; - category: { name: string } | null; } | undefined; @@ -256,14 +249,17 @@ export async function updateTransactionAction( return { success: false, error: "Lançamento não encontrado." }; } - const categoriasProtegidasEdicao = ["Saldo inicial", "Pagamentos"]; - if ( - existing.category?.name && - categoriasProtegidasEdicao.includes(existing.category.name) - ) { + if (existing.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) { return { success: false, - error: `Lançamentos com a categoria '${existing.category.name}' não podem ser editados.`, + error: "Pagamentos automáticos de fatura não podem ser editados.", + }; + } + + if (isInitialBalanceTransaction(existing)) { + return { + success: false, + error: "Lançamentos de saldo inicial não podem ser editados.", }; } @@ -391,13 +387,6 @@ export async function deleteTransactionAction( eq(transactions.id, data.id), eq(transactions.userId, user.id), ), - with: { - category: { - columns: { - name: true, - }, - }, - }, })) as | { id: string; @@ -411,7 +400,6 @@ export async function deleteTransactionAction( period: string; note: string | null; categoryId: string | null; - category: { name: string } | null; } | undefined; @@ -419,14 +407,17 @@ export async function deleteTransactionAction( return { success: false, error: "Lançamento não encontrado." }; } - const categoriasProtegidasRemocao = ["Saldo inicial", "Pagamentos"]; - if ( - existing.category?.name && - categoriasProtegidasRemocao.includes(existing.category.name) - ) { + if (existing.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) { return { success: false, - error: `Lançamentos com a categoria '${existing.category.name}' não podem ser removidos.`, + error: "Pagamentos automáticos de fatura não podem ser removidos.", + }; + } + + if (isInitialBalanceTransaction(existing)) { + return { + success: false, + error: "Lançamentos de saldo inicial não podem ser removidos.", }; } diff --git a/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx b/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx index d2871f4..170553a 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx +++ b/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx @@ -1,7 +1,13 @@ "use client"; +import { useState } from "react"; import { TRANSACTION_CONDITIONS } from "@/features/transactions/lib/constants"; import { Label } from "@/shared/components/ui/label"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/shared/components/ui/popover"; import { Select, SelectContent, @@ -14,6 +20,61 @@ import { cn } from "@/shared/utils/ui"; import { ConditionSelectContent } from "../../select-items"; import type { ConditionSectionProps } from "./transaction-dialog-types"; +function InlineStartInstallmentPicker({ + value, + options, + onChange, +}: { + value: string; + options: number[]; + onChange: (value: string) => void; +}) { + const [open, setOpen] = useState(false); + const selected = Number(value || "1"); + const selectedLabel = + !Number.isNaN(selected) && selected > 0 + ? `${selected}ª parcela` + : "1ª parcela"; + const disabled = options.length === 0; + + return ( +
+ Começar em + + + + + +
+ {options.map((option) => ( + + ))} +
+
+
+
+ ); +} + export function ConditionSection({ formState, onFieldChange, @@ -37,11 +98,17 @@ export function ConditionSection({ const installmentSummary = showInstallments && formState.installmentCount && - amount && !Number.isNaN(installmentCount) && installmentCount > 0 ? getInstallmentLabel(installmentCount) : null; + const startInstallmentOptions = + showInstallments && + formState.installmentCount && + !Number.isNaN(installmentCount) && + installmentCount > 0 + ? Array.from({ length: installmentCount }, (_, index) => index + 1) + : []; return (
@@ -96,6 +163,11 @@ export function ConditionSection({ })} + onFieldChange("startInstallment", value)} + />
) : null} diff --git a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts index f1d1aaf..194ece3 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts +++ b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts @@ -17,6 +17,7 @@ export interface TransactionDialogProps { estabelecimentos: string[]; transaction?: TransactionItem; defaultPeriod?: string; + defaultAccountId?: string | null; defaultCardId?: string | null; defaultPaymentMethod?: string | null; defaultPurchaseDate?: string | null; diff --git a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx index 3ff83ad..7170a24 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx +++ b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx @@ -65,6 +65,7 @@ export function TransactionDialog({ estabelecimentos, transaction, defaultPeriod, + defaultAccountId, defaultCardId, defaultPaymentMethod, defaultPurchaseDate, @@ -88,6 +89,7 @@ export function TransactionDialog({ const [formState, setFormState] = useState(() => buildTransactionInitialState(transaction, defaultPayerId, defaultPeriod, { + defaultAccountId, defaultCardId, defaultPaymentMethod, defaultPurchaseDate, @@ -112,6 +114,7 @@ export function TransactionDialog({ defaultPayerId, defaultPeriod, { + defaultAccountId, defaultCardId, defaultPaymentMethod, defaultPurchaseDate, @@ -151,6 +154,7 @@ export function TransactionDialog({ transaction, defaultPayerId, defaultPeriod, + defaultAccountId, defaultCardId, defaultPaymentMethod, defaultPurchaseDate, @@ -327,6 +331,12 @@ export function TransactionDialog({ formState.condition === "Parcelado" && formState.installmentCount ? Number(formState.installmentCount) : undefined, + startInstallment: + mode === "create" && + formState.condition === "Parcelado" && + formState.startInstallment + ? Number(formState.startInstallment) + : undefined, recurrenceCount: formState.condition === "Recorrente" && formState.recurrenceCount ? Number(formState.recurrenceCount) diff --git a/src/features/transactions/components/page/transactions-page.tsx b/src/features/transactions/components/page/transactions-page.tsx index f3c541d..bb944d7 100644 --- a/src/features/transactions/components/page/transactions-page.tsx +++ b/src/features/transactions/components/page/transactions-page.tsx @@ -63,6 +63,7 @@ interface TransactionsPageProps { categoryFilterOptions: TransactionFilterOption[]; accountCardFilterOptions: AccountCardFilterOption[]; selectedPeriod: string; + defaultAccountId?: string | null; estabelecimentos: string[]; allowCreate?: boolean; noteAsColumn?: boolean; @@ -96,6 +97,7 @@ export function TransactionsPage({ categoryFilterOptions, accountCardFilterOptions, selectedPeriod, + defaultAccountId, estabelecimentos, allowCreate = true, noteAsColumn = false, @@ -562,6 +564,7 @@ export function TransactionsPage({ categoryOptions={categoryOptions} estabelecimentos={estabelecimentos} defaultPeriod={selectedPeriod} + defaultAccountId={defaultAccountId} defaultCardId={defaultCardId} defaultPaymentMethod={defaultPaymentMethod} lockCardSelection={lockCardSelection} @@ -585,6 +588,7 @@ export function TransactionsPage({ categoryOptions={categoryOptions} estabelecimentos={estabelecimentos} defaultPeriod={selectedPeriod} + defaultAccountId={defaultAccountId} defaultCardId={defaultCardId} defaultPaymentMethod={defaultPaymentMethod} lockCardSelection={lockCardSelection} @@ -648,6 +652,7 @@ export function TransactionsPage({ estabelecimentos={estabelecimentos} transaction={transactionToCopy ?? undefined} defaultPeriod={selectedPeriod} + defaultAccountId={defaultAccountId} maxSizeMb={attachmentMaxSizeMb} /> @@ -669,6 +674,7 @@ export function TransactionsPage({ estabelecimentos={estabelecimentos} transaction={transactionToImport ?? undefined} defaultPeriod={selectedPeriod} + defaultAccountId={defaultAccountId} isImporting={true} maxSizeMb={attachmentMaxSizeMb} /> @@ -697,6 +703,7 @@ export function TransactionsPage({ estabelecimentos={estabelecimentos} transaction={selectedTransaction ?? undefined} defaultPeriod={selectedPeriod} + defaultAccountId={defaultAccountId} onBulkEditRequest={handleBulkEditRequest} onSplitEditRequest={handleSplitEditRequest} maxSizeMb={attachmentMaxSizeMb} diff --git a/src/features/transactions/components/shared/anticipation-card.tsx b/src/features/transactions/components/shared/anticipation-card.tsx index f65a6e1..f640e3d 100644 --- a/src/features/transactions/components/shared/anticipation-card.tsx +++ b/src/features/transactions/components/shared/anticipation-card.tsx @@ -1,6 +1,6 @@ "use client"; -import { RiCalendarCheckLine, RiCloseLine, RiEyeLine } from "@remixicon/react"; +import { RiCalendarCheckLine } from "@remixicon/react"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; import { useTransition } from "react"; @@ -164,16 +164,14 @@ export function AnticipationCard({ onClick={handleViewLancamento} disabled={isPending} > - - Ver Lançamento + Cancelar {canCancel && ( - - Cancelar Antecipação + Desfazer Antecipação } title="Cancelar antecipação?" diff --git a/src/features/transactions/components/table/transactions-columns.tsx b/src/features/transactions/components/table/transactions-columns.tsx index 606111f..ef3c79d 100644 --- a/src/features/transactions/components/table/transactions-columns.tsx +++ b/src/features/transactions/components/table/transactions-columns.tsx @@ -426,7 +426,7 @@ function buildColumns({ const initial = displayName.charAt(0).toUpperCase() || "?"; const content = ( <> - + {initial} @@ -477,15 +477,21 @@ function buildColumns({ const content = ( {logoSrc && ( - {`Logo + + + + {label} + + )} - {label} + + {label} + ); @@ -503,7 +509,7 @@ function buildColumns({ return ( - + {content} @@ -654,14 +660,14 @@ function buildColumns({ Editar )} - {row.original.categoriaName !== "Pagamentos" && + {!row.original.readonly && row.original.userId === currentUserId && ( handleCopy(row.original)}> Copiar )} - {row.original.categoriaName !== "Pagamentos" && + {!row.original.readonly && row.original.userId !== currentUserId && ( handleImport(row.original)}> diff --git a/src/features/transactions/components/table/transactions-table.tsx b/src/features/transactions/components/table/transactions-table.tsx index b3493c4..b1679c8 100644 --- a/src/features/transactions/components/table/transactions-table.tsx +++ b/src/features/transactions/components/table/transactions-table.tsx @@ -174,7 +174,7 @@ export function TransactionsTable({ : getPaginationRowModel(), manualPagination: isServerPaginated, pageCount: serverPagination?.totalPages, - enableRowSelection: true, + enableRowSelection: (row) => !row.original.readonly, }); const rowModel = table.getRowModel(); diff --git a/src/features/transactions/lib/form-helpers.ts b/src/features/transactions/lib/form-helpers.ts index 47de855..a161a97 100644 --- a/src/features/transactions/lib/form-helpers.ts +++ b/src/features/transactions/lib/form-helpers.ts @@ -80,6 +80,7 @@ export type TransactionFormState = { cardId: string | undefined; categoryId: string | undefined; installmentCount: string; + startInstallment: string; recurrenceCount: string; dueDate: string; boletoPaymentDate: string; @@ -92,6 +93,7 @@ export type TransactionFormState = { */ type TransactionFormOverrides = { defaultCardId?: string | null; + defaultAccountId?: string | null; defaultPaymentMethod?: string | null; defaultPurchaseDate?: string | null; defaultName?: string | null; @@ -178,7 +180,9 @@ export function buildTransactionInitialState( ? undefined : isImporting ? undefined - : (transaction?.accountId ?? undefined), + : (transaction?.accountId ?? + overrides?.defaultAccountId ?? + undefined), cardId: paymentMethod === "Cartão de crédito" ? isImporting @@ -191,6 +195,12 @@ export function buildTransactionInitialState( installmentCount: transaction?.installmentCount ? String(transaction.installmentCount) : "", + startInstallment: + isImporting && + transaction?.condition === "Parcelado" && + transaction.currentInstallment + ? String(transaction.currentInstallment) + : "1", recurrenceCount: transaction?.recurrenceCount ? String(transaction.recurrenceCount) : "", @@ -252,12 +262,25 @@ export function applyFieldDependencies( if (key === "condition" && typeof value === "string") { if (value !== "Parcelado") { updates.installmentCount = ""; + updates.startInstallment = "1"; } if (value !== "Recorrente") { updates.recurrenceCount = ""; } } + if (key === "installmentCount" && typeof value === "string" && value) { + const nextCount = Number.parseInt(value, 10); + const currentStart = Number.parseInt(currentState.startInstallment, 10); + if ( + !Number.isNaN(nextCount) && + !Number.isNaN(currentStart) && + currentStart > nextCount + ) { + updates.startInstallment = String(nextCount); + } + } + // When payment method changes, adjust related fields if (key === "paymentMethod" && typeof value === "string") { if (value === "Cartão de crédito") { diff --git a/src/features/transactions/lib/page-helpers.ts b/src/features/transactions/lib/page-helpers.ts index aea82cb..2684d03 100644 --- a/src/features/transactions/lib/page-helpers.ts +++ b/src/features/transactions/lib/page-helpers.ts @@ -27,7 +27,13 @@ import { TRANSACTION_CONDITIONS, TRANSACTION_TYPES, } from "@/features/transactions/lib/constants"; -import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants"; +import { + ACCOUNT_AUTO_INVOICE_NOTE_PREFIX, + INITIAL_BALANCE_CONDITION, + INITIAL_BALANCE_NOTE, + INITIAL_BALANCE_PAYMENT_METHOD, + INITIAL_BALANCE_TRANSACTION_TYPE, +} from "@/shared/lib/accounts/constants"; import { PAYER_ROLE_ADMIN, PAYER_ROLE_THIRD_PARTY, @@ -551,8 +557,10 @@ export const mapTransactionsData = (rows: TransactionRowWithRelations[]) => hasAttachments: item.hasAttachments ?? false, readonly: Boolean(item.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) || - item.category?.name === "Saldo inicial" || - item.category?.name === "Pagamentos", + (item.note === INITIAL_BALANCE_NOTE && + item.transactionType === INITIAL_BALANCE_TRANSACTION_TYPE && + item.condition === INITIAL_BALANCE_CONDITION && + item.paymentMethod === INITIAL_BALANCE_PAYMENT_METHOD), })); const sortByLabel = (items: T[]) =>