mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-07-09 11:26:00 +00:00
refactor: faxina arquitetural — código morto, identificadores em inglês e estrutura padronizada
Refatoração estrutural sem mudanças funcionais. Saldo líquido: −428 linhas. Removido: - 14 funções/constantes mortas verificadas via grep no repo todo: validateCategoriaOwnership, getInstallmentAnticipationsAction, getAnticipationDetailsAction, formatDecimalForDb, currencyFormatterNoCents, optionalDecimalSchema, formatMonthLabel, getGoalProgressStatusColorClass, MONTH_PERIOD_PARAM, calculateRemainingInstallments, e 5 funções fetch* não usadas em inbox/queries.ts. - 1 tipo morto (ImportRow) + 2 órfãos consequentes (InstallmentAnticipationWithRelations, GoalProgressStatus convertido em interno). - ~30 export keywords desnecessários (símbolos usados apenas no próprio arquivo). - Re-exports mortos em barrels: EstablishmentLogoPicker, CategoryReportSkeleton, WidgetSkeleton, toNameKey. - Arquivo features/reports/types.ts (barrel inteiro era órfão). Padronizado (PT-BR→EN em identificadores expostos): - 4 constantes globais (LANCAMENTOS_* → TRANSACTIONS_*). - 12 tipos/interfaces (Lancamento*/Pagador*/Estabelecimento* → equivalentes EN). - 13 funções/components exportados (fetchPagador*, EstabelecimentoInput, PagadorInfoCard, etc.). - 5 props cross-file (preLancamentosCount → inboxPendingCount, pagadorAvatarUrl → payerAvatarUrl, etc.). - Mantidas em PT-BR conforme exceção do CLAUDE.md: variáveis locais (pagador, categoria, lancamento), accessor key pagadorName (persistida em preferências), strings de UI. Reorganizado: - transactions/: 14 helpers soltos na raiz movidos para lib/; barrel actions.ts reduzido de 76 linhas de wrappers para 14 linhas de re-exports puros; anticipation-actions.ts movido para actions/anticipation.ts. - dashboard/: 8 helpers soltos consolidados em dashboard/lib/. - reports/: 5 query files na raiz consolidados em reports/lib/. - payers/: detail-actions.ts (21KB) e detail-queries.ts movidos para payers/lib/. - shared/components/: 9 dos 16 componentes soltos agrupados em brand/, widgets/, feedback/. - shared/lib/fetch-json.ts movido para shared/utils/fetch-json.ts. Validação: pnpm exec tsc --noEmit (0 erros), biome check (0 issues), knip (sem unused). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
439
src/features/transactions/actions/anticipation.ts
Normal file
439
src/features/transactions/actions/anticipation.ts
Normal file
@@ -0,0 +1,439 @@
|
||||
"use server";
|
||||
|
||||
import { and, asc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
categories,
|
||||
installmentAnticipations,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
} from "@/shared/lib/actions/helpers";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import {
|
||||
generateAnticipationDescription,
|
||||
generateAnticipationNote,
|
||||
} from "@/shared/lib/installments/anticipation-helpers";
|
||||
import type {
|
||||
CancelAnticipationInput,
|
||||
CreateAnticipationInput,
|
||||
EligibleInstallment,
|
||||
} from "@/shared/lib/installments/anticipation-types";
|
||||
import { uuidSchema } from "@/shared/lib/schemas/common";
|
||||
import type { ActionResult } from "@/shared/lib/types/actions";
|
||||
import { formatDecimalForDbRequired } from "@/shared/utils/currency";
|
||||
|
||||
/**
|
||||
* Schema de validação para criar antecipação
|
||||
*/
|
||||
const createAnticipationSchema = z.object({
|
||||
seriesId: uuidSchema("Série"),
|
||||
installmentIds: z
|
||||
.array(uuidSchema("Parcela"))
|
||||
.min(1, "Selecione pelo menos uma parcela para antecipar."),
|
||||
anticipationPeriod: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(\d{4})-(\d{2})$/, {
|
||||
message: "Selecione um período válido.",
|
||||
}),
|
||||
discount: z.coerce
|
||||
.number()
|
||||
.min(0, "Informe um desconto maior ou igual a zero.")
|
||||
.optional()
|
||||
.default(0),
|
||||
payerId: uuidSchema("Payer").optional(),
|
||||
categoryId: uuidSchema("Category").optional(),
|
||||
note: z.string().trim().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema de validação para cancelar antecipação
|
||||
*/
|
||||
const cancelAnticipationSchema = z.object({
|
||||
anticipationId: uuidSchema("Antecipação"),
|
||||
});
|
||||
|
||||
/**
|
||||
* Busca parcelas elegíveis para antecipação de uma série
|
||||
*/
|
||||
export async function getEligibleInstallmentsAction(
|
||||
seriesId: string,
|
||||
): Promise<ActionResult<EligibleInstallment[]>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
|
||||
// Validar seriesId
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
|
||||
// Buscar todas as parcelas da série que estão elegíveis
|
||||
const rows = await db.query.transactions.findMany({
|
||||
where: and(
|
||||
eq(transactions.seriesId, validatedSeriesId),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.condition, "Parcelado"),
|
||||
// Apenas parcelas não pagas e não antecipadas
|
||||
or(eq(transactions.isSettled, false), isNull(transactions.isSettled)),
|
||||
eq(transactions.isAnticipated, false),
|
||||
),
|
||||
orderBy: [asc(transactions.currentInstallment)],
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
amount: true,
|
||||
period: true,
|
||||
purchaseDate: true,
|
||||
dueDate: true,
|
||||
currentInstallment: true,
|
||||
installmentCount: true,
|
||||
paymentMethod: true,
|
||||
categoryId: true,
|
||||
payerId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const eligibleInstallments: EligibleInstallment[] = rows.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
amount: row.amount,
|
||||
period: row.period,
|
||||
purchaseDate: row.purchaseDate,
|
||||
dueDate: row.dueDate,
|
||||
currentInstallment: row.currentInstallment,
|
||||
installmentCount: row.installmentCount,
|
||||
paymentMethod: row.paymentMethod,
|
||||
categoryId: row.categoryId,
|
||||
payerId: row.payerId,
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Parcelas elegíveis carregadas.",
|
||||
data: eligibleInstallments,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error) as ActionResult<EligibleInstallment[]>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria uma antecipação de parcelas
|
||||
*/
|
||||
export async function createInstallmentAnticipationAction(
|
||||
input: CreateAnticipationInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = createAnticipationSchema.parse(input);
|
||||
|
||||
if (data.payerId || data.categoryId) {
|
||||
const [payer, category] = await Promise.all([
|
||||
data.payerId
|
||||
? db
|
||||
.select({ id: payers.id })
|
||||
.from(payers)
|
||||
.where(
|
||||
and(eq(payers.id, data.payerId), eq(payers.userId, user.id)),
|
||||
)
|
||||
.limit(1)
|
||||
: Promise.resolve([]),
|
||||
data.categoryId
|
||||
? db
|
||||
.select({ id: categories.id })
|
||||
.from(categories)
|
||||
.where(
|
||||
and(
|
||||
eq(categories.id, data.categoryId),
|
||||
eq(categories.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
if (data.payerId && payer.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Pessoa inválida para esta conta.",
|
||||
};
|
||||
}
|
||||
|
||||
if (data.categoryId && category.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria inválida para esta conta.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Validar parcelas selecionadas
|
||||
const installments = await db.query.transactions.findMany({
|
||||
where: and(
|
||||
inArray(transactions.id, data.installmentIds),
|
||||
eq(transactions.userId, user.id),
|
||||
eq(transactions.seriesId, data.seriesId),
|
||||
or(eq(transactions.isSettled, false), isNull(transactions.isSettled)),
|
||||
eq(transactions.isAnticipated, false),
|
||||
),
|
||||
});
|
||||
|
||||
if (installments.length !== data.installmentIds.length) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Algumas parcelas não estão elegíveis para antecipação.",
|
||||
};
|
||||
}
|
||||
|
||||
if (installments.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Nenhuma parcela selecionada para antecipação.",
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Calcular valor total
|
||||
const totalAmountCents = installments.reduce(
|
||||
(sum, inst) => sum + Number(inst.amount) * 100,
|
||||
0,
|
||||
);
|
||||
const totalAmount = totalAmountCents / 100;
|
||||
const totalAmountAbs = Math.abs(totalAmount);
|
||||
|
||||
// 2.1. Aplicar desconto
|
||||
const discount = data.discount || 0;
|
||||
|
||||
// 2.2. Validar que o desconto não é maior que o valor absoluto total
|
||||
if (discount > totalAmountAbs) {
|
||||
return {
|
||||
success: false,
|
||||
error: "O desconto não pode ser maior que o valor total das parcelas.",
|
||||
};
|
||||
}
|
||||
|
||||
// 2.3. Calcular valor final (se negativo, soma o desconto para reduzir a despesa)
|
||||
const finalAmount =
|
||||
totalAmount < 0
|
||||
? totalAmount + discount // Despesa: -1000 + 20 = -980
|
||||
: totalAmount - discount; // Receita: 1000 - 20 = 980
|
||||
|
||||
// 3. Pegar dados da primeira parcela para referência
|
||||
const firstInstallment = installments[0];
|
||||
|
||||
// 4. Criar lançamento e antecipação em transação
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
// 4.1. Criar o lançamento de antecipação (com desconto aplicado)
|
||||
const [newLancamento] = (await tx
|
||||
.insert(transactions)
|
||||
.values({
|
||||
name: generateAnticipationDescription(
|
||||
firstInstallment.name,
|
||||
installments.length,
|
||||
),
|
||||
condition: "À vista",
|
||||
transactionType: firstInstallment.transactionType,
|
||||
paymentMethod: firstInstallment.paymentMethod,
|
||||
amount: formatDecimalForDbRequired(finalAmount),
|
||||
purchaseDate: new Date(),
|
||||
period: data.anticipationPeriod,
|
||||
dueDate: null,
|
||||
isSettled: false,
|
||||
payerId: data.payerId ?? firstInstallment.payerId,
|
||||
categoryId: data.categoryId ?? firstInstallment.categoryId,
|
||||
cardId: firstInstallment.cardId,
|
||||
accountId: firstInstallment.accountId,
|
||||
note:
|
||||
data.note ||
|
||||
generateAnticipationNote(
|
||||
installments.map((inst) => ({
|
||||
id: inst.id,
|
||||
name: inst.name,
|
||||
amount: inst.amount,
|
||||
period: inst.period,
|
||||
purchaseDate: inst.purchaseDate,
|
||||
dueDate: inst.dueDate,
|
||||
currentInstallment: inst.currentInstallment,
|
||||
installmentCount: inst.installmentCount,
|
||||
paymentMethod: inst.paymentMethod,
|
||||
categoryId: inst.categoryId,
|
||||
payerId: inst.payerId,
|
||||
})),
|
||||
),
|
||||
userId: user.id,
|
||||
installmentCount: null,
|
||||
currentInstallment: null,
|
||||
recurrenceCount: null,
|
||||
isAnticipated: false,
|
||||
isDivided: false,
|
||||
seriesId: null,
|
||||
transferId: null,
|
||||
anticipationId: null,
|
||||
boletoPaymentDate: null,
|
||||
})
|
||||
.returning()) as Array<typeof transactions.$inferSelect>;
|
||||
|
||||
// 4.2. Criar registro de antecipação
|
||||
const [anticipation] = (await tx
|
||||
.insert(installmentAnticipations)
|
||||
.values({
|
||||
seriesId: data.seriesId,
|
||||
anticipationPeriod: data.anticipationPeriod,
|
||||
anticipationDate: new Date(),
|
||||
anticipatedInstallmentIds: data.installmentIds,
|
||||
totalAmount: formatDecimalForDbRequired(totalAmount),
|
||||
installmentCount: installments.length,
|
||||
discount: formatDecimalForDbRequired(discount),
|
||||
transactionId: newLancamento.id,
|
||||
payerId: data.payerId ?? firstInstallment.payerId,
|
||||
categoryId: data.categoryId ?? firstInstallment.categoryId,
|
||||
note: data.note || null,
|
||||
userId: user.id,
|
||||
})
|
||||
.returning()) as Array<typeof installmentAnticipations.$inferSelect>;
|
||||
|
||||
// 4.3. Marcar parcelas como antecipadas e zerar seus valores
|
||||
await tx
|
||||
.update(transactions)
|
||||
.set({
|
||||
isAnticipated: true,
|
||||
anticipationId: anticipation.id,
|
||||
amount: "0", // Zera o valor para não contar em dobro
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
inArray(transactions.id, data.installmentIds),
|
||||
eq(transactions.userId, user.id),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
revalidateForEntity("transactions", user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `${installments.length} ${
|
||||
installments.length === 1
|
||||
? "parcela antecipada"
|
||||
: "parcelas antecipadas"
|
||||
} com sucesso!`,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancela uma antecipação de parcelas
|
||||
* Remove o lançamento de antecipação e restaura as parcelas originais
|
||||
*/
|
||||
export async function cancelInstallmentAnticipationAction(
|
||||
input: CancelAnticipationInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = cancelAnticipationSchema.parse(input);
|
||||
|
||||
await db.transaction(async (tx: typeof db) => {
|
||||
// 1. Buscar antecipação usando query builder
|
||||
const anticipationRows = await tx
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
seriesId: installmentAnticipations.seriesId,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
anticipatedInstallmentIds:
|
||||
installmentAnticipations.anticipatedInstallmentIds,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
discount: installmentAnticipations.discount,
|
||||
transactionId: installmentAnticipations.transactionId,
|
||||
payerId: installmentAnticipations.payerId,
|
||||
categoryId: installmentAnticipations.categoryId,
|
||||
note: installmentAnticipations.note,
|
||||
userId: installmentAnticipations.userId,
|
||||
createdAt: installmentAnticipations.createdAt,
|
||||
transaction: transactions,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
transactions,
|
||||
eq(installmentAnticipations.transactionId, transactions.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.id, data.anticipationId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
const anticipation = anticipationRows[0];
|
||||
|
||||
if (!anticipation) {
|
||||
throw new Error("Antecipação não encontrada.");
|
||||
}
|
||||
|
||||
// 2. Verificar se o lançamento já foi pago
|
||||
if (anticipation.transaction?.isSettled === true) {
|
||||
throw new Error(
|
||||
"Não é possível cancelar uma antecipação já paga. Remova o pagamento primeiro.",
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Calcular valor original por parcela (totalAmount sem desconto / quantidade)
|
||||
const originalTotalAmount = Number(anticipation.totalAmount);
|
||||
const originalValuePerInstallment =
|
||||
originalTotalAmount / anticipation.installmentCount;
|
||||
|
||||
// 4. Remover flag de antecipação e restaurar valores das parcelas
|
||||
await tx
|
||||
.update(transactions)
|
||||
.set({
|
||||
isAnticipated: false,
|
||||
anticipationId: null,
|
||||
amount: formatDecimalForDbRequired(originalValuePerInstallment),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
inArray(
|
||||
transactions.id,
|
||||
anticipation.anticipatedInstallmentIds as string[],
|
||||
),
|
||||
eq(transactions.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
// 5. Deletar lançamento de antecipação
|
||||
await tx
|
||||
.delete(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(transactions.id, anticipation.transactionId),
|
||||
eq(transactions.userId, user.id),
|
||||
),
|
||||
);
|
||||
|
||||
// 6. Deletar registro de antecipação
|
||||
await tx
|
||||
.delete(installmentAnticipations)
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.id, data.anticipationId),
|
||||
eq(installmentAnticipations.userId, user.id),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
revalidateForEntity("transactions", user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Antecipação cancelada com sucesso!",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { attachments, transactionAttachments, transactions } from "@/db/schema";
|
||||
import {
|
||||
ALLOWED_MIME_TYPES,
|
||||
MAX_FILE_SIZE,
|
||||
} from "@/features/transactions/attachments-config";
|
||||
} from "@/features/transactions/lib/attachments-config";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
} from "@/features/transactions/lib/constants";
|
||||
import { handleActionError } from "@/shared/lib/actions/helpers";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { db } from "@/shared/lib/db";
|
||||
@@ -705,7 +705,7 @@ export async function createMassTransactionsAction(
|
||||
await sendPayerAutoEmails({
|
||||
userLabel: resolveUserLabel(user),
|
||||
action: "created",
|
||||
entriesByPagador: notificationEntries,
|
||||
entriesByPayer: notificationEntries,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -799,7 +799,7 @@ export async function deleteMultipleTransactionsAction(
|
||||
await sendPayerAutoEmails({
|
||||
userLabel: resolveUserLabel(user),
|
||||
action: "deleted",
|
||||
entriesByPagador: notificationEntries,
|
||||
entriesByPayer: notificationEntries,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
PAYMENT_METHODS,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
} from "@/features/transactions/lib/constants";
|
||||
import {
|
||||
INITIAL_BALANCE_CONDITION,
|
||||
INITIAL_BALANCE_NOTE,
|
||||
@@ -31,7 +31,7 @@ import { addMonthsToPeriod, MONTH_NAMES } from "@/shared/utils/period";
|
||||
// Authorization Validation Functions
|
||||
// ============================================================================
|
||||
|
||||
export async function validatePagadorOwnership(
|
||||
export async function validatePayerOwnership(
|
||||
userId: string,
|
||||
payerId: string | null | undefined,
|
||||
): Promise<boolean> {
|
||||
@@ -65,19 +65,6 @@ export async function fetchOwnedPayerIds(
|
||||
return new Set(rows.map((row) => row.id));
|
||||
}
|
||||
|
||||
export async function validateCategoriaOwnership(
|
||||
userId: string,
|
||||
categoryId: string | null | undefined,
|
||||
): Promise<boolean> {
|
||||
if (!categoryId) return true;
|
||||
|
||||
const categoria = await db.query.categories.findFirst({
|
||||
where: and(eq(categories.id, categoryId), eq(categories.userId, userId)),
|
||||
});
|
||||
|
||||
return !!categoria;
|
||||
}
|
||||
|
||||
export async function fetchOwnedCategoryIds(
|
||||
userId: string,
|
||||
categoryIds: Array<string | null | undefined>,
|
||||
@@ -298,10 +285,10 @@ export const resolvePeriod = (purchaseDate: string, period?: string | null) => {
|
||||
return `${year}-${month}`;
|
||||
};
|
||||
|
||||
export const isValidDateInput = (value: string) =>
|
||||
const isValidDateInput = (value: string) =>
|
||||
!Number.isNaN(parseLocalDateString(value).getTime());
|
||||
|
||||
export const baseFields = z.object({
|
||||
const baseFields = z.object({
|
||||
purchaseDate: z
|
||||
.string({ message: "Informe a data da transação." })
|
||||
.trim()
|
||||
@@ -498,7 +485,7 @@ export const toggleSettlementSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type BaseInput = z.infer<typeof baseFields>;
|
||||
type BaseInput = z.infer<typeof baseFields>;
|
||||
export type CreateInput = z.infer<typeof createSchema>;
|
||||
export type UpdateInput = z.infer<typeof updateSchema>;
|
||||
export type DeleteInput = z.infer<typeof deleteSchema>;
|
||||
@@ -527,7 +514,7 @@ type InitialCandidate = {
|
||||
paymentMethod: string | null;
|
||||
};
|
||||
|
||||
export const isInitialBalanceLancamento = (record?: InitialCandidate | null) =>
|
||||
export const isInitialBalanceTransaction = (record?: InitialCandidate | null) =>
|
||||
!!record &&
|
||||
record.note === INITIAL_BALANCE_NOTE &&
|
||||
record.transactionType === INITIAL_BALANCE_TRANSACTION_TYPE &&
|
||||
@@ -554,7 +541,7 @@ const splitAmount = (totalCents: number, parts: number) => {
|
||||
);
|
||||
};
|
||||
|
||||
export type Share = {
|
||||
type Share = {
|
||||
payerId: string | null;
|
||||
amountCents: number;
|
||||
};
|
||||
@@ -617,7 +604,7 @@ type BuildTransactionRecordsParams = {
|
||||
|
||||
export type TransactionInsert = typeof transactions.$inferInsert;
|
||||
|
||||
export const buildLancamentoRecords = ({
|
||||
export const buildTransactionRecords = ({
|
||||
data,
|
||||
userId,
|
||||
period,
|
||||
@@ -859,7 +846,7 @@ export const updateBulkSchema = z.object({
|
||||
|
||||
export type UpdateBulkInput = z.infer<typeof updateBulkSchema>;
|
||||
|
||||
export const massAddTransactionSchema = z.object({
|
||||
const massAddTransactionSchema = z.object({
|
||||
purchaseDate: z
|
||||
.string({ message: "Informe a data da transação." })
|
||||
.trim()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { fetchAccountLancamentos } from "@/features/accounts/statement-queries";
|
||||
import type { TransactionsExportContext } from "@/features/transactions/export-types";
|
||||
import { fetchAccountTransactions } from "@/features/accounts/statement-queries";
|
||||
import type { TransactionsExportContext } from "@/features/transactions/lib/export-types";
|
||||
import {
|
||||
buildSluggedFilters,
|
||||
buildSlugMaps,
|
||||
buildTransactionWhere,
|
||||
mapTransactionsData,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchTransactionFilterSources,
|
||||
fetchTransactions,
|
||||
@@ -66,7 +66,7 @@ export async function exportTransactionsDataAction(
|
||||
|
||||
const rows =
|
||||
validated.source === "account-statement"
|
||||
? await fetchAccountLancamentos(filters, validated.settledOnly ?? true)
|
||||
? await fetchAccountTransactions(filters, validated.settledOnly ?? true)
|
||||
: await fetchTransactions(filters);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { transactions } from "@/db/schema";
|
||||
import { mapTransactionsData } from "@/features/transactions/page-helpers";
|
||||
import { mapTransactionsData } from "@/features/transactions/lib/page-helpers";
|
||||
import { fetchTransactionsWithRelations } from "@/features/transactions/queries";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import type { TransactionItem } from "../components/types";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { transactions } from "@/db/schema";
|
||||
import {
|
||||
validateCartaoOwnership,
|
||||
validateContaOwnership,
|
||||
validatePagadorOwnership,
|
||||
validatePayerOwnership,
|
||||
} from "@/features/transactions/actions/core";
|
||||
import { revalidateForEntity } from "@/shared/lib/actions/helpers";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
@@ -36,8 +36,7 @@ const importSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type ImportRow = z.infer<typeof importRowSchema>;
|
||||
export type ImportInput = z.infer<typeof importSchema>;
|
||||
type ImportInput = z.infer<typeof importSchema>;
|
||||
|
||||
type ImportResult =
|
||||
| { success: true; imported: number; skipped: number; importBatchId: string }
|
||||
@@ -79,7 +78,7 @@ export async function importTransactionsAction(
|
||||
|
||||
// Valida ownership
|
||||
const [payerOk, accountOk, cardOk] = await Promise.all([
|
||||
validatePagadorOwnership(userId, payerId),
|
||||
validatePayerOwnership(userId, payerId),
|
||||
validateContaOwnership(userId, accountId),
|
||||
validateCartaoOwnership(userId, cardId),
|
||||
]);
|
||||
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
getBusinessTodayDate,
|
||||
parseLocalDateString,
|
||||
} from "@/shared/utils/date";
|
||||
import { copyAttachmentsForImport } from "../attachment-copy";
|
||||
import { copyAttachmentsForImport } from "../lib/attachment-copy";
|
||||
import { cleanupAttachmentsAfterTransactionDelete } from "./attachments";
|
||||
import {
|
||||
buildLancamentoRecords,
|
||||
buildShares,
|
||||
buildTransactionRecords,
|
||||
type CreateInput,
|
||||
centsToDecimalString,
|
||||
createSchema,
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
deleteSchema,
|
||||
formatPaidInvoicePeriods,
|
||||
getPaidInvoicePeriods,
|
||||
isInitialBalanceLancamento,
|
||||
isInitialBalanceTransaction,
|
||||
resolvePeriod,
|
||||
resolveUserLabel,
|
||||
revalidate,
|
||||
@@ -95,7 +95,7 @@ export async function createTransactionAction(
|
||||
data.condition === "Parcelado" || data.condition === "Recorrente";
|
||||
const seriesId = isSeriesLancamento ? randomUUID() : null;
|
||||
|
||||
const records = buildLancamentoRecords({
|
||||
const records = buildTransactionRecords({
|
||||
data,
|
||||
userId: user.id,
|
||||
period,
|
||||
@@ -180,7 +180,7 @@ export async function createTransactionAction(
|
||||
await sendPayerAutoEmails({
|
||||
userLabel: resolveUserLabel(user),
|
||||
action: "created",
|
||||
entriesByPagador: notificationEntries,
|
||||
entriesByPayer: notificationEntries,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ export async function updateTransactionAction(
|
||||
and(eq(transactions.id, data.id), eq(transactions.userId, user.id)),
|
||||
);
|
||||
|
||||
if (isInitialBalanceLancamento(existing) && existing.accountId) {
|
||||
if (isInitialBalanceTransaction(existing) && existing.accountId) {
|
||||
const updatedInitialBalance = formatDecimalForDbRequired(
|
||||
Math.abs(data.amount ?? 0),
|
||||
);
|
||||
@@ -465,7 +465,7 @@ export async function deleteTransactionAction(
|
||||
await sendPayerAutoEmails({
|
||||
userLabel: resolveUserLabel(user),
|
||||
action: "deleted",
|
||||
entriesByPagador: notificationEntries,
|
||||
entriesByPayer: notificationEntries,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user