feat(transactions): edição cooperativa e visibilidade de divisões

Adiciona splitGroupId para vincular as duas shares de um lançamento
dividido (schema + índice + migration 0026). Habilita:

- Edição de par dividido com escolha de escopo (apenas este lado ou
  ambos) via novo SplitPairDialog e updateTransactionSplitPairAction
- Filtro "Somente divididos" (isDivided) na tabela de lançamentos
- Visibilidade de anexos para pessoas com acesso compartilhado via
  payerShares; upload e detach em massa expandem para shares irmãs
- Cópia independente de anexos no fluxo "Importar para Minha Conta"
  (novo fileKey, novo userId, S3 CopyObject) com seção read-only
  "Anexos que serão copiados" no dialog de importação
- Ícone de clipe na tabela de lançamentos da página da pessoa via
  EXISTS em fetchPagadorLancamentos

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-25 14:45:35 +00:00
parent 5b03824a72
commit b14f487824
20 changed files with 3595 additions and 86 deletions

View File

@@ -1,6 +1,7 @@
import { and, eq } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { attachments, transactionAttachments, transactions } from "@/db/schema";
import { db } from "@/shared/lib/db";
import { getPayerAccess } from "@/shared/lib/payers/access";
import { createPresignedGetUrl } from "@/shared/lib/storage/presign";
export type TransactionAttachmentListItem = {
@@ -17,16 +18,24 @@ export async function fetchTransactionAttachments(
transactionId: string,
): Promise<TransactionAttachmentListItem[]> {
const [transaction] = await db
.select({ id: transactions.id })
.select({
id: transactions.id,
userId: transactions.userId,
payerId: transactions.payerId,
})
.from(transactions)
.where(
and(eq(transactions.id, transactionId), eq(transactions.userId, userId)),
);
.where(eq(transactions.id, transactionId));
if (!transaction) {
return [];
}
if (transaction.userId !== userId) {
if (!transaction.payerId) return [];
const access = await getPayerAccess(userId, transaction.payerId);
if (!access) return [];
}
const rows = await db
.select({
attachmentId: transactionAttachments.attachmentId,
@@ -37,19 +46,9 @@ export async function fetchTransactionAttachments(
createdAt: attachments.createdAt,
})
.from(transactionAttachments)
.innerJoin(
transactions,
and(
eq(transactionAttachments.transactionId, transactions.id),
eq(transactions.userId, userId),
),
)
.innerJoin(
attachments,
and(
eq(transactionAttachments.attachmentId, attachments.id),
eq(attachments.userId, userId),
),
eq(transactionAttachments.attachmentId, attachments.id),
)
.where(eq(transactionAttachments.transactionId, transactionId));