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 && (
-
+
+
+
+ {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[]) =>