mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
refactor: traduz dominio de payers no app
This commit is contained in:
@@ -68,7 +68,7 @@ export function createSidebarNavData(
|
||||
.map((pagador) => ({
|
||||
title: pagador.name?.trim().length
|
||||
? pagador.name.trim()
|
||||
: "Pagador sem nome",
|
||||
: "Payer sem nome",
|
||||
url: `/payers/${pagador.id}`,
|
||||
key: pagador.canEdit ? pagador.id : `${pagador.id}-shared`,
|
||||
isShared: !pagador.canEdit,
|
||||
|
||||
@@ -115,7 +115,7 @@ export const auth = betterAuth({
|
||||
/**
|
||||
* Após criar novo usuário, inicializa:
|
||||
* 1. Categorias padrão (Receitas/Despesas)
|
||||
* 2. Pagador padrão (vinculado ao usuário)
|
||||
* 2. Payer padrão (vinculado ao usuário)
|
||||
*/
|
||||
after: async (user) => {
|
||||
// Se falhar aqui, o usuário já foi criado - considere usar queue para retry
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
compartilhamentosPagador,
|
||||
pagadores,
|
||||
user as usersTable,
|
||||
} from "@/db/schema";
|
||||
import { payerShares, payers, user as usersTable } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
export type PagadorWithAccess = typeof pagadores.$inferSelect & {
|
||||
export type PayerWithAccess = Omit<typeof payers.$inferSelect, "shareCode"> & {
|
||||
shareCode: string | null;
|
||||
canEdit: boolean;
|
||||
sharedByName: string | null;
|
||||
sharedByEmail: string | null;
|
||||
shareId: string | null;
|
||||
};
|
||||
|
||||
export async function fetchPagadoresWithAccess(
|
||||
export async function fetchPayersWithAccess(
|
||||
userId: string,
|
||||
): Promise<PagadorWithAccess[]> {
|
||||
): Promise<PayerWithAccess[]> {
|
||||
const [owned, shared] = await Promise.all([
|
||||
db.query.pagadores.findMany({
|
||||
where: eq(pagadores.userId, userId),
|
||||
db.query.payers.findMany({
|
||||
where: eq(payers.userId, userId),
|
||||
}),
|
||||
db
|
||||
.select({
|
||||
shareId: compartilhamentosPagador.id,
|
||||
pagador: pagadores,
|
||||
shareId: payerShares.id,
|
||||
payer: payers,
|
||||
ownerName: usersTable.name,
|
||||
ownerEmail: usersTable.email,
|
||||
})
|
||||
.from(compartilhamentosPagador)
|
||||
.innerJoin(
|
||||
pagadores,
|
||||
eq(compartilhamentosPagador.pagadorId, pagadores.id),
|
||||
)
|
||||
.leftJoin(usersTable, eq(pagadores.userId, usersTable.id))
|
||||
.where(eq(compartilhamentosPagador.sharedWithUserId, userId)),
|
||||
.from(payerShares)
|
||||
.innerJoin(payers, eq(payerShares.payerId, payers.id))
|
||||
.leftJoin(usersTable, eq(payers.userId, usersTable.id))
|
||||
.where(eq(payerShares.sharedWithUserId, userId)),
|
||||
]);
|
||||
|
||||
const ownedMapped: PagadorWithAccess[] = owned.map((item) => ({
|
||||
const ownedMapped: PayerWithAccess[] = owned.map((item) => ({
|
||||
...item,
|
||||
canEdit: true,
|
||||
sharedByName: null,
|
||||
@@ -44,8 +38,8 @@ export async function fetchPagadoresWithAccess(
|
||||
shareId: null,
|
||||
}));
|
||||
|
||||
const sharedMapped: PagadorWithAccess[] = shared.map((item) => ({
|
||||
...item.pagador,
|
||||
const sharedMapped: PayerWithAccess[] = shared.map((item) => ({
|
||||
...(item.payer as typeof payers.$inferSelect),
|
||||
shareCode: null,
|
||||
canEdit: false,
|
||||
sharedByName: item.ownerName ?? null,
|
||||
@@ -56,9 +50,9 @@ export async function fetchPagadoresWithAccess(
|
||||
return [...ownedMapped, ...sharedMapped];
|
||||
}
|
||||
|
||||
export async function getPagadorAccess(userId: string, pagadorId: string) {
|
||||
const pagador = await db.query.pagadores.findFirst({
|
||||
where: and(eq(pagadores.id, pagadorId)),
|
||||
export async function getPayerAccess(userId: string, payerId: string) {
|
||||
const pagador = await db.query.payers.findFirst({
|
||||
where: and(eq(payers.id, payerId)),
|
||||
});
|
||||
|
||||
if (!pagador) {
|
||||
@@ -69,14 +63,14 @@ export async function getPagadorAccess(userId: string, pagadorId: string) {
|
||||
return {
|
||||
pagador,
|
||||
canEdit: true,
|
||||
share: null as typeof compartilhamentosPagador.$inferSelect | null,
|
||||
share: null as typeof payerShares.$inferSelect | null,
|
||||
};
|
||||
}
|
||||
|
||||
const share = await db.query.compartilhamentosPagador.findFirst({
|
||||
const share = await db.query.payerShares.findFirst({
|
||||
where: and(
|
||||
eq(compartilhamentosPagador.pagadorId, pagadorId),
|
||||
eq(compartilhamentosPagador.sharedWithUserId, userId),
|
||||
eq(payerShares.payerId, payerId),
|
||||
eq(payerShares.sharedWithUserId, userId),
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const PAGADOR_STATUS_OPTIONS = ["Ativo", "Inativo"] as const;
|
||||
export const PAYER_STATUS_OPTIONS = ["Ativo", "Inativo"] as const;
|
||||
|
||||
export type PagadorStatus = (typeof PAGADOR_STATUS_OPTIONS)[number];
|
||||
export type PayerStatus = (typeof PAYER_STATUS_OPTIONS)[number];
|
||||
|
||||
export const PAGADOR_ROLE_ADMIN = "admin";
|
||||
export const PAGADOR_ROLE_TERCEIRO = "terceiro";
|
||||
export const DEFAULT_PAGADOR_AVATAR = "default_icon.png";
|
||||
export const PAYER_ROLE_ADMIN = "admin";
|
||||
export const PAYER_ROLE_THIRD_PARTY = "terceiro";
|
||||
export const DEFAULT_PAYER_AVATAR = "default_icon.png";
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { pagadores } from "@/db/schema";
|
||||
import { payers } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import {
|
||||
DEFAULT_PAGADOR_AVATAR,
|
||||
PAGADOR_ROLE_ADMIN,
|
||||
PAGADOR_STATUS_OPTIONS,
|
||||
DEFAULT_PAYER_AVATAR,
|
||||
PAYER_ROLE_ADMIN,
|
||||
PAYER_STATUS_OPTIONS,
|
||||
} from "./constants";
|
||||
import { normalizeNameFromEmail } from "./utils";
|
||||
|
||||
const DEFAULT_STATUS = PAGADOR_STATUS_OPTIONS[0];
|
||||
const DEFAULT_STATUS = PAYER_STATUS_OPTIONS[0];
|
||||
|
||||
interface SeedUserLike {
|
||||
id?: string;
|
||||
@@ -24,9 +24,9 @@ export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasAnyPagador = await db.query.pagadores.findFirst({
|
||||
const hasAnyPagador = await db.query.payers.findFirst({
|
||||
columns: { id: true, role: true },
|
||||
where: eq(pagadores.userId, userId),
|
||||
where: eq(payers.userId, userId),
|
||||
});
|
||||
|
||||
if (hasAnyPagador) {
|
||||
@@ -36,16 +36,16 @@ export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
|
||||
const name =
|
||||
(user.name && user.name.trim().length > 0
|
||||
? user.name.trim()
|
||||
: normalizeNameFromEmail(user.email)) || "Pagador principal";
|
||||
: normalizeNameFromEmail(user.email)) || "Payer principal";
|
||||
|
||||
// Usa a imagem do Google se disponível, senão usa o avatar padrão
|
||||
const avatarUrl = user.image ?? DEFAULT_PAGADOR_AVATAR;
|
||||
const avatarUrl = user.image ?? DEFAULT_PAYER_AVATAR;
|
||||
|
||||
await db.insert(pagadores).values({
|
||||
await db.insert(payers).values({
|
||||
name,
|
||||
email: user.email ?? null,
|
||||
status: DEFAULT_STATUS,
|
||||
role: PAGADOR_ROLE_ADMIN,
|
||||
role: PAYER_ROLE_ADMIN,
|
||||
avatarUrl,
|
||||
note: null,
|
||||
isAutoSend: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
sql,
|
||||
sum,
|
||||
} from "drizzle-orm";
|
||||
import { cartoes, lancamentos } from "@/db/schema";
|
||||
import { cards, transactions } from "@/db/schema";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { toDateOnlyString } from "@/shared/utils/date";
|
||||
@@ -27,27 +27,27 @@ const DESPESA = "Despesa";
|
||||
const PAYMENT_METHOD_CARD = "Cartão de crédito";
|
||||
const PAYMENT_METHOD_BOLETO = "Boleto";
|
||||
|
||||
export type PagadorMonthlyBreakdown = {
|
||||
export type PayerMonthlyBreakdown = {
|
||||
totalExpenses: number;
|
||||
totalIncomes: number;
|
||||
paymentSplits: Record<"card" | "boleto" | "instant", number>;
|
||||
};
|
||||
|
||||
export type PagadorHistoryPoint = {
|
||||
export type PayerHistoryPoint = {
|
||||
period: string;
|
||||
label: string;
|
||||
receitas: number;
|
||||
despesas: number;
|
||||
};
|
||||
|
||||
export type PagadorCardUsageItem = {
|
||||
export type PayerCardUsageItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
logo: string | null;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type PagadorBoletoStats = {
|
||||
export type PayerBoletoStats = {
|
||||
totalAmount: number;
|
||||
paidAmount: number;
|
||||
pendingAmount: number;
|
||||
@@ -55,7 +55,7 @@ export type PagadorBoletoStats = {
|
||||
pendingCount: number;
|
||||
};
|
||||
|
||||
export type PagadorBoletoItem = {
|
||||
export type PayerBoletoItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
@@ -64,7 +64,7 @@ export type PagadorBoletoItem = {
|
||||
isSettled: boolean;
|
||||
};
|
||||
|
||||
export type PagadorPaymentStatusData = {
|
||||
export type PayerPaymentStatusData = {
|
||||
paidAmount: number;
|
||||
paidCount: number;
|
||||
pendingAmount: number;
|
||||
@@ -74,39 +74,39 @@ export type PagadorPaymentStatusData = {
|
||||
|
||||
const excludeAutoInvoiceEntries = () =>
|
||||
or(
|
||||
isNull(lancamentos.note),
|
||||
not(ilike(lancamentos.note, `${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`)),
|
||||
isNull(transactions.note),
|
||||
not(ilike(transactions.note, `${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`)),
|
||||
);
|
||||
|
||||
type BaseFilters = {
|
||||
userId: string;
|
||||
pagadorId: string;
|
||||
payerId: string;
|
||||
period: string;
|
||||
};
|
||||
|
||||
export async function fetchPagadorMonthlyBreakdown({
|
||||
export async function fetchPayerMonthlyBreakdown({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
}: BaseFilters): Promise<PagadorMonthlyBreakdown> {
|
||||
}: BaseFilters): Promise<PayerMonthlyBreakdown> {
|
||||
const rows = await db
|
||||
.select({
|
||||
paymentMethod: lancamentos.paymentMethod,
|
||||
transactionType: lancamentos.transactionType,
|
||||
totalAmount: sum(lancamentos.amount).as("total"),
|
||||
paymentMethod: transactions.paymentMethod,
|
||||
transactionType: transactions.transactionType,
|
||||
totalAmount: sum(transactions.amount).as("total"),
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
eq(transactions.period, period),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.paymentMethod, lancamentos.transactionType);
|
||||
.groupBy(transactions.paymentMethod, transactions.transactionType);
|
||||
|
||||
const paymentSplits: PagadorMonthlyBreakdown["paymentSplits"] = {
|
||||
const paymentSplits: PayerMonthlyBreakdown["paymentSplits"] = {
|
||||
card: 0,
|
||||
boleto: 0,
|
||||
instant: 0,
|
||||
@@ -137,12 +137,12 @@ export async function fetchPagadorMonthlyBreakdown({
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchPagadorHistory({
|
||||
export async function fetchPayerHistory({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
months = 6,
|
||||
}: BaseFilters & { months?: number }): Promise<PagadorHistoryPoint[]> {
|
||||
}: BaseFilters & { months?: number }): Promise<PayerHistoryPoint[]> {
|
||||
const startPeriod = addMonthsToPeriod(period, -(Math.max(months, 1) - 1));
|
||||
const windowPeriods = buildPeriodRange(startPeriod, period);
|
||||
const start = windowPeriods[0];
|
||||
@@ -150,21 +150,21 @@ export async function fetchPagadorHistory({
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
period: lancamentos.period,
|
||||
transactionType: lancamentos.transactionType,
|
||||
totalAmount: sum(lancamentos.amount).as("total"),
|
||||
period: transactions.period,
|
||||
transactionType: transactions.transactionType,
|
||||
totalAmount: sum(transactions.amount).as("total"),
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
gte(lancamentos.period, start),
|
||||
lte(lancamentos.period, end),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
gte(transactions.period, start),
|
||||
lte(transactions.period, end),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.period, lancamentos.transactionType);
|
||||
.groupBy(transactions.period, transactions.transactionType);
|
||||
|
||||
const totalsByPeriod = new Map<
|
||||
string,
|
||||
@@ -198,38 +198,38 @@ export async function fetchPagadorHistory({
|
||||
|
||||
export async function fetchPagadorCardUsage({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
}: BaseFilters): Promise<PagadorCardUsageItem[]> {
|
||||
}: BaseFilters): Promise<PayerCardUsageItem[]> {
|
||||
const rows = await db
|
||||
.select({
|
||||
cartaoId: lancamentos.cartaoId,
|
||||
cardName: cartoes.name,
|
||||
cardLogo: cartoes.logo,
|
||||
totalAmount: sum(lancamentos.amount).as("total"),
|
||||
cardId: transactions.cardId,
|
||||
cardName: cards.name,
|
||||
cardLogo: cards.logo,
|
||||
totalAmount: sum(transactions.amount).as("total"),
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(cartoes, eq(lancamentos.cartaoId, cartoes.id))
|
||||
.from(transactions)
|
||||
.innerJoin(cards, eq(transactions.cardId, cards.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.paymentMethod, PAYMENT_METHOD_CARD),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
eq(transactions.period, period),
|
||||
eq(transactions.paymentMethod, PAYMENT_METHOD_CARD),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.cartaoId, cartoes.name, cartoes.logo);
|
||||
.groupBy(transactions.cardId, cards.name, cards.logo);
|
||||
|
||||
const items: PagadorCardUsageItem[] = [];
|
||||
const items: PayerCardUsageItem[] = [];
|
||||
|
||||
for (const row of rows) {
|
||||
if (!row.cartaoId) {
|
||||
if (!row.cardId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push({
|
||||
id: row.cartaoId,
|
||||
id: row.cardId,
|
||||
name: row.cardName ?? "Cartão",
|
||||
logo: row.cardLogo ?? null,
|
||||
amount: Math.abs(toNumber(row.totalAmount)),
|
||||
@@ -241,26 +241,26 @@ export async function fetchPagadorCardUsage({
|
||||
|
||||
export async function fetchPagadorBoletoStats({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
}: BaseFilters): Promise<PagadorBoletoStats> {
|
||||
}: BaseFilters): Promise<PayerBoletoStats> {
|
||||
const rows = await db
|
||||
.select({
|
||||
isSettled: lancamentos.isSettled,
|
||||
totalAmount: sum(lancamentos.amount).as("total"),
|
||||
totalCount: sql<number>`count(${lancamentos.id})`.as("count"),
|
||||
isSettled: transactions.isSettled,
|
||||
totalAmount: sum(transactions.amount).as("total"),
|
||||
totalCount: sql<number>`count(${transactions.id})`.as("count"),
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.paymentMethod, PAYMENT_METHOD_BOLETO),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
eq(transactions.period, period),
|
||||
eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
)
|
||||
.groupBy(lancamentos.isSettled);
|
||||
.groupBy(transactions.isSettled);
|
||||
|
||||
let paidAmount = 0;
|
||||
let pendingAmount = 0;
|
||||
@@ -290,31 +290,31 @@ export async function fetchPagadorBoletoStats({
|
||||
|
||||
export async function fetchPagadorBoletoItems({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
}: BaseFilters): Promise<PagadorBoletoItem[]> {
|
||||
}: BaseFilters): Promise<PayerBoletoItem[]> {
|
||||
const rows = await db
|
||||
.select({
|
||||
id: lancamentos.id,
|
||||
name: lancamentos.name,
|
||||
amount: lancamentos.amount,
|
||||
dueDate: lancamentos.dueDate,
|
||||
boletoPaymentDate: lancamentos.boletoPaymentDate,
|
||||
isSettled: lancamentos.isSettled,
|
||||
id: transactions.id,
|
||||
name: transactions.name,
|
||||
amount: transactions.amount,
|
||||
dueDate: transactions.dueDate,
|
||||
boletoPaymentDate: transactions.boletoPaymentDate,
|
||||
isSettled: transactions.isSettled,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.paymentMethod, PAYMENT_METHOD_BOLETO),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
eq(transactions.period, period),
|
||||
eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(lancamentos.dueDate));
|
||||
.orderBy(asc(transactions.dueDate));
|
||||
|
||||
const items: PagadorBoletoItem[] = [];
|
||||
const items: PayerBoletoItem[] = [];
|
||||
|
||||
for (const row of rows) {
|
||||
items.push({
|
||||
@@ -332,23 +332,23 @@ export async function fetchPagadorBoletoItems({
|
||||
|
||||
export async function fetchPagadorPaymentStatus({
|
||||
userId,
|
||||
pagadorId,
|
||||
payerId,
|
||||
period,
|
||||
}: BaseFilters): Promise<PagadorPaymentStatusData> {
|
||||
}: BaseFilters): Promise<PayerPaymentStatusData> {
|
||||
const rows = await db
|
||||
.select({
|
||||
paidAmount: sql<string>`coalesce(sum(case when ${lancamentos.isSettled} = true then abs(${lancamentos.amount}) else 0 end), 0)`,
|
||||
paidCount: sql<number>`sum(case when ${lancamentos.isSettled} = true then 1 else 0 end)`,
|
||||
pendingAmount: sql<string>`coalesce(sum(case when (${lancamentos.isSettled} = false or ${lancamentos.isSettled} is null) then abs(${lancamentos.amount}) else 0 end), 0)`,
|
||||
pendingCount: sql<number>`sum(case when (${lancamentos.isSettled} = false or ${lancamentos.isSettled} is null) then 1 else 0 end)`,
|
||||
paidAmount: sql<string>`coalesce(sum(case when ${transactions.isSettled} = true then abs(${transactions.amount}) else 0 end), 0)`,
|
||||
paidCount: sql<number>`sum(case when ${transactions.isSettled} = true then 1 else 0 end)`,
|
||||
pendingAmount: sql<string>`coalesce(sum(case when (${transactions.isSettled} = false or ${transactions.isSettled} is null) then abs(${transactions.amount}) else 0 end), 0)`,
|
||||
pendingCount: sql<number>`sum(case when (${transactions.isSettled} = false or ${transactions.isSettled} is null) then 1 else 0 end)`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.pagadorId, pagadorId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.transactionType, DESPESA),
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.payerId, payerId),
|
||||
eq(transactions.period, period),
|
||||
eq(transactions.transactionType, DESPESA),
|
||||
excludeAutoInvoiceEntries(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { cache } from "react";
|
||||
import { pagadores } from "@/db/schema";
|
||||
import { payers } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
|
||||
/**
|
||||
* Returns the admin pagador ID for a user (cached per request via React.cache).
|
||||
* Eliminates the need for JOIN with pagadores in ~20 dashboard queries.
|
||||
* Eliminates the need for JOIN with payers in ~20 dashboard queries.
|
||||
*/
|
||||
export const getAdminPagadorId = cache(
|
||||
export const getAdminPayerId = cache(
|
||||
async (userId: string): Promise<string | null> => {
|
||||
const [row] = await db
|
||||
.select({ id: pagadores.id })
|
||||
.from(pagadores)
|
||||
.where(
|
||||
and(
|
||||
eq(pagadores.userId, userId),
|
||||
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
|
||||
),
|
||||
)
|
||||
.select({ id: payers.id })
|
||||
.from(payers)
|
||||
.where(and(eq(payers.userId, userId), eq(payers.role, PAYER_ROLE_ADMIN)))
|
||||
.limit(1);
|
||||
return row?.id ?? null;
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inArray } from "drizzle-orm";
|
||||
import { Resend } from "resend";
|
||||
import { pagadores } from "@/db/schema";
|
||||
import { payers } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { getResendFromEmail } from "@/shared/lib/email/resend";
|
||||
import { formatCurrency } from "@/shared/utils/currency";
|
||||
@@ -9,7 +9,7 @@ import { formatDateTime } from "@/shared/utils/date";
|
||||
type ActionType = "created" | "deleted";
|
||||
|
||||
export type NotificationEntry = {
|
||||
pagadorId: string;
|
||||
payerId: string;
|
||||
name: string | null;
|
||||
amount: number;
|
||||
transactionType: string | null;
|
||||
@@ -20,13 +20,13 @@ export type NotificationEntry = {
|
||||
note: string | null;
|
||||
};
|
||||
|
||||
export type PagadorNotificationRequest = {
|
||||
export type PayerNotificationRequest = {
|
||||
userLabel: string;
|
||||
action: ActionType;
|
||||
entriesByPagador: Map<string, NotificationEntry[]>;
|
||||
};
|
||||
|
||||
type PagadorNotificationRecipient = {
|
||||
type PayerNotificationRecipient = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
@@ -110,11 +110,11 @@ const buildHtmlBody = ({
|
||||
`;
|
||||
};
|
||||
|
||||
export async function sendPagadorAutoEmails({
|
||||
export async function sendPayerAutoEmails({
|
||||
userLabel,
|
||||
action,
|
||||
entriesByPagador,
|
||||
}: PagadorNotificationRequest) {
|
||||
}: PayerNotificationRequest) {
|
||||
"use server";
|
||||
|
||||
if (entriesByPagador.size === 0) {
|
||||
@@ -136,11 +136,11 @@ export async function sendPagadorAutoEmails({
|
||||
return;
|
||||
}
|
||||
|
||||
const pagadorRows = (await db.query.pagadores.findMany({
|
||||
where: inArray(pagadores.id, pagadorIds),
|
||||
})) as PagadorNotificationRecipient[];
|
||||
const payerRows = (await db.query.payers.findMany({
|
||||
where: inArray(payers.id, pagadorIds),
|
||||
})) as PayerNotificationRecipient[];
|
||||
|
||||
if (pagadorRows.length === 0) {
|
||||
if (payerRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,12 +149,12 @@ export async function sendPagadorAutoEmails({
|
||||
action === "created" ? "Novo lançamento" : "Lançamento removido";
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
pagadorRows.map(async (pagador: PagadorNotificationRecipient) => {
|
||||
if (!pagador.email || !pagador.isAutoSend) {
|
||||
payerRows.map(async (payer: PayerNotificationRecipient) => {
|
||||
if (!payer.email || !payer.isAutoSend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = entriesByPagador.get(pagador.id);
|
||||
const entries = entriesByPagador.get(payer.id);
|
||||
if (!entries || entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -167,8 +167,8 @@ export async function sendPagadorAutoEmails({
|
||||
|
||||
await resend.emails.send({
|
||||
from: resendFrom,
|
||||
to: pagador.email,
|
||||
subject: `${subjectPrefix} - ${pagador.name}`,
|
||||
to: payer.email,
|
||||
subject: `${subjectPrefix} - ${payer.name}`,
|
||||
html,
|
||||
});
|
||||
}),
|
||||
@@ -177,7 +177,7 @@ export async function sendPagadorAutoEmails({
|
||||
// Log any failed email sends
|
||||
results.forEach((result: PromiseSettledResult<void>, index: number) => {
|
||||
if (result.status === "rejected") {
|
||||
const pagador = pagadorRows[index];
|
||||
const pagador = payerRows[index];
|
||||
console.error(
|
||||
`Failed to send email notification to ${pagador?.name} (${pagador?.email}):`,
|
||||
result.reason,
|
||||
@@ -187,7 +187,7 @@ export async function sendPagadorAutoEmails({
|
||||
}
|
||||
|
||||
export type RawNotificationRecord = {
|
||||
pagadorId: string | null;
|
||||
payerId: string | null;
|
||||
name: string | null;
|
||||
amount: string | number | null;
|
||||
transactionType: string | null;
|
||||
@@ -198,13 +198,13 @@ export type RawNotificationRecord = {
|
||||
note: string | null;
|
||||
};
|
||||
|
||||
export const buildEntriesByPagador = (
|
||||
export const buildEntriesByPayer = (
|
||||
records: RawNotificationRecord[],
|
||||
): Map<string, NotificationEntry[]> => {
|
||||
const map = new Map<string, NotificationEntry[]>();
|
||||
|
||||
records.forEach((record) => {
|
||||
if (!record.pagadorId) {
|
||||
if (!record.payerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ export const buildEntriesByPagador = (
|
||||
: null;
|
||||
|
||||
const entry: NotificationEntry = {
|
||||
pagadorId: record.pagadorId,
|
||||
payerId: record.payerId,
|
||||
name: record.name ?? null,
|
||||
amount,
|
||||
transactionType: record.transactionType ?? null,
|
||||
@@ -231,9 +231,9 @@ export const buildEntriesByPagador = (
|
||||
note: record.note ?? null,
|
||||
};
|
||||
|
||||
const list = map.get(record.pagadorId) ?? [];
|
||||
const list = map.get(record.payerId) ?? [];
|
||||
list.push(entry);
|
||||
map.set(record.pagadorId, list);
|
||||
map.set(record.payerId, list);
|
||||
});
|
||||
|
||||
return map;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_PAGADOR_AVATAR } from "./constants";
|
||||
import { DEFAULT_PAYER_AVATAR } from "./constants";
|
||||
|
||||
/**
|
||||
* Normaliza o caminho do avatar extraindo apenas o nome do arquivo.
|
||||
@@ -29,7 +29,7 @@ export const normalizeAvatarPath = (
|
||||
*/
|
||||
export const getAvatarSrc = (avatar: string | null | undefined): string => {
|
||||
if (!avatar) {
|
||||
return `/avatars/${DEFAULT_PAGADOR_AVATAR}`;
|
||||
return `/avatars/${DEFAULT_PAYER_AVATAR}`;
|
||||
}
|
||||
|
||||
// Se for uma URL completa (Google, etc), retorna diretamente
|
||||
@@ -43,7 +43,7 @@ export const getAvatarSrc = (avatar: string | null | undefined): string => {
|
||||
|
||||
// Se for um caminho local, normaliza e adiciona o prefixo
|
||||
const normalized = normalizeAvatarPath(avatar);
|
||||
return `/avatars/${normalized ?? DEFAULT_PAGADOR_AVATAR}`;
|
||||
return `/avatars/${normalized ?? DEFAULT_PAYER_AVATAR}`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user