Files
sgse-app/packages/backend/convex/atas.ts

310 lines
7.9 KiB
TypeScript

import { v } from 'convex/values';
import { mutation, query } from './_generated/server';
import type { Id } from './_generated/dataModel';
import { getCurrentUserFunction } from './auth';
import { internal } from './_generated/api';
export const list = query({
args: {
periodoInicio: v.optional(v.string()),
periodoFim: v.optional(v.string()),
numero: v.optional(v.string()),
numeroSei: v.optional(v.string())
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'listar'
});
const numero = args.numero?.trim().toLowerCase();
const numeroSei = args.numeroSei?.trim().toLowerCase();
const periodoInicio = args.periodoInicio || undefined;
const periodoFim = args.periodoFim || undefined;
const atas = await ctx.db.query('atas').collect();
return atas.filter((ata) => {
const numeroOk = !numero || (ata.numero || '').toLowerCase().includes(numero);
const seiOk = !numeroSei || (ata.numeroSei || '').toLowerCase().includes(numeroSei);
// Filtro por intervalo (range): retorna atas cuja vigência intersecta o período informado.
// Considera datas como strings "YYYY-MM-DD" (lexicograficamente comparáveis).
const ataInicio = ata.dataInicio ?? '0000-01-01';
const ataFim = ata.dataFim ?? '9999-12-31';
const periodoOk =
(!periodoInicio && !periodoFim) ||
(periodoInicio && periodoFim && ataInicio <= periodoFim && ataFim >= periodoInicio) ||
(periodoInicio && !periodoFim && ataFim >= periodoInicio) ||
(!periodoInicio && periodoFim && ataInicio <= periodoFim);
return numeroOk && seiOk && periodoOk;
});
}
});
export const get = query({
args: { id: v.id('atas') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'ver'
});
return await ctx.db.get(args.id);
}
});
export const getObjetos = query({
args: { id: v.id('atas') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'ver'
});
const links = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
const objetos = await Promise.all(links.map((link) => ctx.db.get(link.objetoId)));
return objetos.filter((obj) => obj !== null);
}
});
export const listByObjetoIds = query({
args: {
objetoIds: v.array(v.id('objetos'))
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'listar'
});
if (args.objetoIds.length === 0) return [];
// Buscar todos os vínculos ata-objeto para os objetos informados
const links = [];
for (const objetoId of args.objetoIds) {
const partial = await ctx.db
.query('atasObjetos')
.withIndex('by_objetoId', (q) => q.eq('objetoId', objetoId as Id<'objetos'>))
.collect();
links.push(...partial);
}
const ataIds = Array.from(new Set(links.map((l) => l.ataId as Id<'atas'>)));
if (ataIds.length === 0) return [];
const atas = await Promise.all(ataIds.map((id) => ctx.db.get(id)));
return atas.filter((a): a is NonNullable<typeof a> => a !== null);
}
});
export const create = mutation({
args: {
numero: v.string(),
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
empresaId: v.id('empresas'),
numeroSei: v.string(),
objetosIds: v.array(v.id('objetos'))
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'criar'
});
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
const ataId = await ctx.db.insert('atas', {
numero: args.numero,
numeroSei: args.numeroSei,
empresaId: args.empresaId,
dataInicio: args.dataInicio,
dataFim: args.dataFim,
criadoPor: user._id,
criadoEm: Date.now(),
atualizadoEm: Date.now()
});
// Vincular objetos
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId,
objetoId
});
}
return ataId;
}
});
export const update = mutation({
args: {
id: v.id('atas'),
numero: v.string(),
numeroSei: v.string(),
empresaId: v.id('empresas'),
dataInicio: v.optional(v.string()),
dataFim: v.optional(v.string()),
objetosIds: v.array(v.id('objetos'))
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'editar'
});
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
await ctx.db.patch(args.id, {
numero: args.numero,
numeroSei: args.numeroSei,
empresaId: args.empresaId,
dataInicio: args.dataInicio,
dataFim: args.dataFim,
atualizadoEm: Date.now()
});
// Atualizar objetos vinculados
// Primeiro remove todos os vínculos existentes
const existingLinks = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
for (const link of existingLinks) {
await ctx.db.delete(link._id);
}
// Adiciona os novos vínculos
for (const objetoId of args.objetosIds) {
await ctx.db.insert('atasObjetos', {
ataId: args.id,
objetoId
});
}
}
});
export const remove = mutation({
args: { id: v.id('atas') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'excluir'
});
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
// Remover vínculos com objetos
const links = await ctx.db
.query('atasObjetos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
for (const link of links) {
await ctx.db.delete(link._id);
}
// Remover documentos vinculados
const docs = await ctx.db
.query('atasDocumentos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.id))
.collect();
for (const doc of docs) {
await ctx.storage.delete(doc.storageId);
await ctx.db.delete(doc._id);
}
await ctx.db.delete(args.id);
}
});
export const generateUploadUrl = mutation({
args: {},
handler: async (ctx) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'editar'
});
return await ctx.storage.generateUploadUrl();
}
});
export const saveDocumento = mutation({
args: {
ataId: v.id('atas'),
nome: v.string(),
storageId: v.id('_storage'),
tipo: v.string(),
tamanho: v.number()
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'editar'
});
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
return await ctx.db.insert('atasDocumentos', {
ataId: args.ataId,
nome: args.nome,
storageId: args.storageId,
tipo: args.tipo,
tamanho: args.tamanho,
criadoPor: user._id,
criadoEm: Date.now()
});
}
});
export const removeDocumento = mutation({
args: { id: v.id('atasDocumentos') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'editar'
});
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
const doc = await ctx.db.get(args.id);
if (!doc) throw new Error('Documento não encontrado');
await ctx.storage.delete(doc.storageId);
await ctx.db.delete(args.id);
}
});
export const getDocumentos = query({
args: { ataId: v.id('atas') },
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'atas',
acao: 'ver'
});
const docs = await ctx.db
.query('atasDocumentos')
.withIndex('by_ataId', (q) => q.eq('ataId', args.ataId))
.collect();
return await Promise.all(
docs.map(async (doc) => ({
...doc,
url: await ctx.storage.getUrl(doc.storageId)
}))
);
}
});