feat: Implement initial pedido (order) management, product catalog, and TI configuration features.

This commit is contained in:
2025-12-01 17:11:34 -03:00
parent 5e7de6c943
commit b8a67e0a57
18 changed files with 4429 additions and 1934 deletions

View File

@@ -8,6 +8,7 @@
* @module
*/
import type * as acoes from "../acoes.js";
import type * as actions_email from "../actions/email.js";
import type * as actions_linkPreview from "../actions/linkPreview.js";
import type * as actions_pushNotifications from "../actions/pushNotifications.js";
@@ -21,6 +22,7 @@ import type * as auth_utils from "../auth/utils.js";
import type * as chamadas from "../chamadas.js";
import type * as chamados from "../chamados.js";
import type * as chat from "../chat.js";
import type * as config from "../config.js";
import type * as configuracaoEmail from "../configuracaoEmail.js";
import type * as configuracaoJitsi from "../configuracaoJitsi.js";
import type * as configuracaoPonto from "../configuracaoPonto.js";
@@ -43,9 +45,11 @@ import type * as logsAcesso from "../logsAcesso.js";
import type * as logsAtividades from "../logsAtividades.js";
import type * as logsLogin from "../logsLogin.js";
import type * as monitoramento from "../monitoramento.js";
import type * as pedidos from "../pedidos.js";
import type * as permissoesAcoes from "../permissoesAcoes.js";
import type * as pontos from "../pontos.js";
import type * as preferenciasNotificacao from "../preferenciasNotificacao.js";
import type * as produtos from "../produtos.js";
import type * as pushNotifications from "../pushNotifications.js";
import type * as roles from "../roles.js";
import type * as saldoFerias from "../saldoFerias.js";
@@ -67,6 +71,7 @@ import type {
} from "convex/server";
declare const fullApi: ApiFromModules<{
acoes: typeof acoes;
"actions/email": typeof actions_email;
"actions/linkPreview": typeof actions_linkPreview;
"actions/pushNotifications": typeof actions_pushNotifications;
@@ -80,6 +85,7 @@ declare const fullApi: ApiFromModules<{
chamadas: typeof chamadas;
chamados: typeof chamados;
chat: typeof chat;
config: typeof config;
configuracaoEmail: typeof configuracaoEmail;
configuracaoJitsi: typeof configuracaoJitsi;
configuracaoPonto: typeof configuracaoPonto;
@@ -102,9 +108,11 @@ declare const fullApi: ApiFromModules<{
logsAtividades: typeof logsAtividades;
logsLogin: typeof logsLogin;
monitoramento: typeof monitoramento;
pedidos: typeof pedidos;
permissoesAcoes: typeof permissoesAcoes;
pontos: typeof pontos;
preferenciasNotificacao: typeof preferenciasNotificacao;
produtos: typeof produtos;
pushNotifications: typeof pushNotifications;
roles: typeof roles;
saldoFerias: typeof saldoFerias;

View File

@@ -0,0 +1,56 @@
import { mutation, query } from './_generated/server';
import { v } from 'convex/values';
import { getCurrentUserFunction } from './auth';
export const list = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query('acoes').collect();
}
});
export const create = mutation({
args: {
nome: v.string(),
tipo: v.union(v.literal('projeto'), v.literal('lei'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
return await ctx.db.insert('acoes', {
...args,
criadoPor: user._id,
criadoEm: Date.now()
});
}
});
export const update = mutation({
args: {
id: v.id('acoes'),
nome: v.string(),
tipo: v.union(v.literal('projeto'), v.literal('lei'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
await ctx.db.patch(args.id, {
nome: args.nome,
tipo: args.tipo
});
}
});
export const remove = mutation({
args: {
id: v.id('acoes')
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
await ctx.db.delete(args.id);
}
});

View File

@@ -0,0 +1,38 @@
import { mutation, query } from './_generated/server';
import { v } from 'convex/values';
import { getCurrentUserFunction } from './auth';
export const getComprasSetor = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query('config').first();
}
});
export const updateComprasSetor = mutation({
args: {
setorId: v.id('setores')
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
// Check if user has permission (e.g., admin or TI) - For now, assuming any auth user can set it,
// but in production should be restricted.
const existingConfig = await ctx.db.query('config').first();
if (existingConfig) {
await ctx.db.patch(existingConfig._id, {
comprasSetorId: args.setorId,
atualizadoEm: Date.now()
});
} else {
await ctx.db.insert('config', {
comprasSetorId: args.setorId,
criadoPor: user._id,
atualizadoEm: Date.now()
});
}
}
});

View File

@@ -0,0 +1,596 @@
import { mutation, query, internalMutation } from './_generated/server';
import { v } from 'convex/values';
import { getCurrentUserFunction } from './auth';
import { api, internal } from './_generated/api';
import type { Doc, Id } from './_generated/dataModel';
import type { QueryCtx, MutationCtx } from './_generated/server';
// ========== HELPERS ==========
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
return user;
}
// ========== QUERIES ==========
export const list = query({
args: {},
returns: v.array(
v.object({
_id: v.id('pedidos'),
_creationTime: v.number(),
numeroSei: v.optional(v.string()),
status: v.union(
v.literal('em_rascunho'),
v.literal('aguardando_aceite'),
v.literal('em_analise'),
v.literal('precisa_ajustes'),
v.literal('cancelado'),
v.literal('concluido')
),
acaoId: v.optional(v.id('acoes')),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoEm: v.number()
})
),
handler: async (ctx) => {
return await ctx.db.query('pedidos').collect();
}
});
export const get = query({
args: { id: v.id('pedidos') },
returns: v.union(
v.object({
_id: v.id('pedidos'),
_creationTime: v.number(),
numeroSei: v.optional(v.string()),
status: v.union(
v.literal('em_rascunho'),
v.literal('aguardando_aceite'),
v.literal('em_analise'),
v.literal('precisa_ajustes'),
v.literal('cancelado'),
v.literal('concluido')
),
acaoId: v.optional(v.id('acoes')),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoEm: v.number()
}),
v.null()
),
handler: async (ctx, args) => {
return await ctx.db.get(args.id);
}
});
export const getItems = query({
args: { pedidoId: v.id('pedidos') },
returns: v.array(
v.object({
_id: v.id('pedidoItems'),
_creationTime: v.number(),
pedidoId: v.id('pedidos'),
produtoId: v.id('produtos'),
valorEstimado: v.string(),
valorReal: v.optional(v.string()),
quantidade: v.number(),
adicionadoPor: v.id('funcionarios'),
adicionadoPorNome: v.string(),
criadoEm: v.number()
})
),
handler: async (ctx, args) => {
const items = await ctx.db
.query('pedidoItems')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
.collect();
// Get employee names
const itemsWithNames = await Promise.all(
items.map(async (item) => {
const funcionario = await ctx.db.get(item.adicionadoPor);
return {
...item,
adicionadoPorNome: funcionario?.nome || 'Desconhecido'
};
})
);
return itemsWithNames;
}
});
export const getHistory = query({
args: { pedidoId: v.id('pedidos') },
handler: async (ctx, args) => {
const history = await ctx.db
.query('historicoPedidos')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
.order('desc')
.collect();
// Get user names
const historyWithNames = await Promise.all(
history.map(async (entry) => {
const usuario = await ctx.db.get(entry.usuarioId);
return {
_id: entry._id,
_creationTime: entry._creationTime,
pedidoId: entry.pedidoId,
usuarioId: entry.usuarioId,
usuarioNome: usuario?.nome || 'Desconhecido',
acao: entry.acao,
detalhes: entry.detalhes,
data: entry.data
};
})
);
return historyWithNames;
}
});
export const checkExisting = query({
args: {
acaoId: v.optional(v.id('acoes')),
numeroSei: v.optional(v.string()),
produtoIds: v.optional(v.array(v.id('produtos')))
},
returns: v.array(
v.object({
_id: v.id('pedidos'),
_creationTime: v.number(),
numeroSei: v.optional(v.string()),
status: v.union(
v.literal('em_rascunho'),
v.literal('aguardando_aceite'),
v.literal('em_analise'),
v.literal('precisa_ajustes'),
v.literal('cancelado'),
v.literal('concluido')
),
acaoId: v.optional(v.id('acoes')),
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoEm: v.number(),
matchingItems: v.optional(
v.array(
v.object({
produtoId: v.id('produtos'),
quantidade: v.number()
})
)
)
})
),
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) return [];
const openStatuses: Array<
'em_rascunho' | 'aguardando_aceite' | 'em_analise' | 'precisa_ajustes'
> = ['em_rascunho', 'aguardando_aceite', 'em_analise', 'precisa_ajustes'];
// 1) Buscar todos os pedidos "abertos" usando o índice by_status
let pedidosAbertos: Doc<'pedidos'>[] = [];
for (const status of openStatuses) {
const partial = await ctx.db
.query('pedidos')
.withIndex('by_status', (q) => q.eq('status', status))
.collect();
pedidosAbertos = pedidosAbertos.concat(partial);
}
// 2) Filtros opcionais: acaoId e numeroSei
pedidosAbertos = pedidosAbertos.filter((p) => {
if (args.acaoId && p.acaoId !== args.acaoId) return false;
if (args.numeroSei && p.numeroSei !== args.numeroSei) return false;
return true;
});
// 3) Filtro por produtos (se informado) e coleta de matchingItems
const resultados = [];
for (const pedido of pedidosAbertos) {
let include = true;
let matchingItems: { produtoId: Id<'produtos'>; quantidade: number }[] = [];
// Se houver filtro de produtos, verificamos se o pedido tem ALGUM dos produtos
if (args.produtoIds && args.produtoIds.length > 0) {
const items = await ctx.db
.query('pedidoItems')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedido._id))
.collect();
// const pedidoProdutoIds = new Set(items.map((i) => i.produtoId)); // Unused
const matching = items.filter((i) => args.produtoIds?.includes(i.produtoId));
if (matching.length > 0) {
matchingItems = matching.map((i) => ({
produtoId: i.produtoId,
quantidade: i.quantidade
}));
} else {
// Se foi pedido filtro por produtos e não tem nenhum match, ignoramos este pedido
// A MENOS que tenha dado match por numeroSei ou acaoId?
// A regra original era: "Filtro por produtos (se informado)"
// Se o usuário informou produtos, ele quer ver pedidos que tenham esses produtos.
// Mas se ele TAMBÉM informou numeroSei, talvez ele queira ver aquele pedido específico mesmo sem o produto?
// Vamos manter a lógica de "E": se informou produtos, tem que ter o produto.
include = false;
}
}
if (include) {
resultados.push({
_id: pedido._id,
_creationTime: pedido._creationTime,
numeroSei: pedido.numeroSei,
status: pedido.status,
acaoId: pedido.acaoId,
criadoPor: pedido.criadoPor,
criadoEm: pedido.criadoEm,
atualizadoEm: pedido.atualizadoEm,
matchingItems: matchingItems.length > 0 ? matchingItems : undefined
});
}
}
return resultados;
}
});
// ========== MUTATIONS ==========
export const create = mutation({
args: {
numeroSei: v.optional(v.string()),
acaoId: v.optional(v.id('acoes'))
},
returns: v.id('pedidos'),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
// 1. Check Config
const config = await ctx.db.query('config').first();
if (!config || !config.comprasSetorId) {
throw new Error('Setor de Compras não configurado. Contate o administrador.');
}
// 2. Check Existing (Double check)
if (args.acaoId) {
const existing = await ctx.db
.query('pedidos')
.withIndex('by_acaoId', (q) => q.eq('acaoId', args.acaoId))
.filter((q) =>
q.or(
q.eq(q.field('status'), 'em_rascunho'),
q.eq(q.field('status'), 'aguardando_aceite'),
q.eq(q.field('status'), 'em_analise'),
q.eq(q.field('status'), 'precisa_ajustes')
)
)
.first();
if (existing) {
throw new Error('Já existe um pedido em andamento para esta ação.');
}
}
// 3. Create Order
const pedidoId = await ctx.db.insert('pedidos', {
numeroSei: args.numeroSei,
status: 'em_rascunho',
acaoId: args.acaoId,
criadoPor: user._id,
criadoEm: Date.now(),
atualizadoEm: Date.now()
});
// 4. Create History
await ctx.db.insert('historicoPedidos', {
pedidoId,
usuarioId: user._id,
acao: 'criacao',
detalhes: JSON.stringify({ numeroSei: args.numeroSei, acaoId: args.acaoId }),
data: Date.now()
});
return pedidoId;
}
});
export const updateSeiNumber = mutation({
args: {
pedidoId: v.id('pedidos'),
numeroSei: v.string()
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
const pedido = await ctx.db.get(args.pedidoId);
if (!pedido) throw new Error('Pedido not found');
// Check if SEI number is already taken by another order
const existing = await ctx.db
.query('pedidos')
.filter((q) =>
q.and(q.eq(q.field('numeroSei'), args.numeroSei), q.neq(q.field('_id'), args.pedidoId))
)
.first();
if (existing) {
throw new Error('Este número SEI já está em uso por outro pedido.');
}
const oldSei = pedido.numeroSei;
await ctx.db.patch(args.pedidoId, {
numeroSei: args.numeroSei,
atualizadoEm: Date.now()
});
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: user._id,
acao: 'atualizacao_sei',
detalhes: JSON.stringify({ de: oldSei, para: args.numeroSei }),
data: Date.now()
});
}
});
export const addItem = mutation({
args: {
pedidoId: v.id('pedidos'),
produtoId: v.id('produtos'),
valorEstimado: v.string(),
quantidade: v.number()
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
// Ensure user has a funcionarioId linked
if (!user.funcionarioId) {
throw new Error('Usuário não vinculado a um funcionário.');
}
await ctx.db.insert('pedidoItems', {
pedidoId: args.pedidoId,
produtoId: args.produtoId,
valorEstimado: args.valorEstimado,
quantidade: args.quantidade,
adicionadoPor: user.funcionarioId,
criadoEm: Date.now()
});
await ctx.db.patch(args.pedidoId, { atualizadoEm: Date.now() });
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: user._id,
acao: 'adicao_item',
detalhes: JSON.stringify({
produtoId: args.produtoId,
valor: args.valorEstimado,
quantidade: args.quantidade
}),
data: Date.now()
});
}
});
export const updateItemQuantity = mutation({
args: {
itemId: v.id('pedidoItems'),
novaQuantidade: v.number()
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
if (!user.funcionarioId) {
throw new Error('Usuário não vinculado a um funcionário.');
}
const item = await ctx.db.get(args.itemId);
if (!item) throw new Error('Item não encontrado.');
const quantidadeAnterior = item.quantidade;
// Check permission: only item owner can decrease quantity
const isOwner = item.adicionadoPor === user.funcionarioId;
const isDecreasing = args.novaQuantidade < quantidadeAnterior;
if (isDecreasing && !isOwner) {
throw new Error(
'Apenas quem adicionou este item pode diminuir a quantidade. Você pode apenas aumentar.'
);
}
// Update quantity
await ctx.db.patch(args.itemId, { quantidade: args.novaQuantidade });
await ctx.db.patch(item.pedidoId, { atualizadoEm: Date.now() });
// Create history entry
await ctx.db.insert('historicoPedidos', {
pedidoId: item.pedidoId,
usuarioId: user._id,
acao: 'alteracao_quantidade',
detalhes: JSON.stringify({
produtoId: item.produtoId,
quantidadeAnterior,
novaQuantidade: args.novaQuantidade
}),
data: Date.now()
});
}
});
export const removeItem = mutation({
args: {
itemId: v.id('pedidoItems')
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
const item = await ctx.db.get(args.itemId);
if (!item) throw new Error('Item not found');
await ctx.db.delete(args.itemId);
await ctx.db.patch(item.pedidoId, { atualizadoEm: Date.now() });
await ctx.db.insert('historicoPedidos', {
pedidoId: item.pedidoId,
usuarioId: user._id,
acao: 'remocao_item',
detalhes: JSON.stringify({ produtoId: item.produtoId, valor: item.valorEstimado }),
data: Date.now()
});
}
});
export const updateStatus = mutation({
args: {
pedidoId: v.id('pedidos'),
novoStatus: v.union(
v.literal('em_rascunho'),
v.literal('aguardando_aceite'),
v.literal('em_analise'),
v.literal('precisa_ajustes'),
v.literal('cancelado'),
v.literal('concluido')
)
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await getUsuarioAutenticado(ctx);
const pedido = await ctx.db.get(args.pedidoId);
if (!pedido) throw new Error('Pedido not found');
const oldStatus = pedido.status;
await ctx.db.patch(args.pedidoId, {
status: args.novoStatus,
atualizadoEm: Date.now()
});
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: user._id,
acao: 'alteracao_status',
detalhes: JSON.stringify({ de: oldStatus, para: args.novoStatus }),
data: Date.now()
});
// Trigger Notifications
await ctx.scheduler.runAfter(0, internal.pedidos.notifyStatusChange, {
pedidoId: args.pedidoId,
oldStatus,
newStatus: args.novoStatus,
actorId: user._id
});
}
});
// ========== INTERNAL (NOTIFICATIONS) ==========
export const notifyStatusChange = internalMutation({
args: {
pedidoId: v.id('pedidos'),
oldStatus: v.string(),
newStatus: v.string(),
actorId: v.id('usuarios')
},
returns: v.null(),
handler: async (ctx, args) => {
const pedido = await ctx.db.get(args.pedidoId);
if (!pedido) return;
const actor = await ctx.db.get(args.actorId);
const actorName = actor ? actor.nome : 'Alguém';
const recipients = new Set<string>(); // Set of User IDs
// 1. If status is "aguardando_aceite", notify Purchasing Sector
if (args.newStatus === 'aguardando_aceite') {
const config = await ctx.db.query('config').first();
if (config && config.comprasSetorId) {
// Find all employees in this sector
const funcionarioSetores = await ctx.db
.query('funcionarioSetores')
.withIndex('by_setorId', (q) => q.eq('setorId', config.comprasSetorId!))
.collect();
const funcionarioIds = funcionarioSetores.map((fs) => fs.funcionarioId);
// Find users linked to these employees
for (const fId of funcionarioIds) {
const user = await ctx.db
.query('usuarios')
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', fId))
.first();
if (user) recipients.add(user._id);
}
}
}
// 2. Notify "Involved" users (Creator + Item Adders)
// Always notify creator (unless they are the actor)
if (pedido.criadoPor !== args.actorId) {
recipients.add(pedido.criadoPor);
}
// Notify item adders
const items = await ctx.db
.query('pedidoItems')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
.collect();
for (const item of items) {
const user = await ctx.db
.query('usuarios')
.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', item.adicionadoPor))
.first();
if (user && user._id !== args.actorId) {
recipients.add(user._id);
}
}
// Send Notifications
for (const recipientId of recipients) {
const recipientIdTyped = recipientId as Id<'usuarios'>;
// 1. In-App Notification
await ctx.db.insert('notificacoes', {
usuarioId: recipientIdTyped,
tipo: 'alerta_seguranca', // Using alerta_seguranca as the closest match for system notifications
titulo: `Pedido ${pedido.numeroSei || 'sem número SEI'} atualizado`,
descricao: `Status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
lida: false,
criadaEm: Date.now(),
remetenteId: args.actorId
});
// 2. Email Notification (Async)
const recipientUser = await ctx.db.get(recipientIdTyped);
if (recipientUser && recipientUser.email) {
// Using enfileirarEmail directly
await ctx.scheduler.runAfter(0, api.email.enfileirarEmail, {
destinatario: recipientUser.email,
destinatarioId: recipientIdTyped,
assunto: `Atualização no Pedido ${pedido.numeroSei || 'sem número SEI'}`,
corpo: `O pedido ${pedido.numeroSei || 'sem número SEI'} teve seu status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
enviadoPor: args.actorId
});
}
}
}
});

View File

@@ -395,6 +395,100 @@ const PERMISSOES_BASE = {
recurso: 'fluxos_documentos',
acao: 'excluir',
descricao: 'Excluir documentos de fluxos'
},
// Pedidos
{
nome: 'pedidos.listar',
recurso: 'pedidos',
acao: 'listar',
descricao: 'Listar pedidos'
},
{
nome: 'pedidos.criar',
recurso: 'pedidos',
acao: 'criar',
descricao: 'Criar novos pedidos'
},
{
nome: 'pedidos.ver',
recurso: 'pedidos',
acao: 'ver',
descricao: 'Visualizar detalhes de pedidos'
},
{
nome: 'pedidos.editar_status',
recurso: 'pedidos',
acao: 'editar_status',
descricao: 'Alterar status de pedidos'
},
{
nome: 'pedidos.adicionar_item',
recurso: 'pedidos',
acao: 'adicionar_item',
descricao: 'Adicionar itens ao pedido'
},
{
nome: 'pedidos.remover_item',
recurso: 'pedidos',
acao: 'remover_item',
descricao: 'Remover itens do pedido'
},
// Produtos
{
nome: 'produtos.listar',
recurso: 'produtos',
acao: 'listar',
descricao: 'Listar produtos'
},
{
nome: 'produtos.criar',
recurso: 'produtos',
acao: 'criar',
descricao: 'Criar novos produtos'
},
{
nome: 'produtos.editar',
recurso: 'produtos',
acao: 'editar',
descricao: 'Editar produtos'
},
{
nome: 'produtos.excluir',
recurso: 'produtos',
acao: 'excluir',
descricao: 'Excluir produtos'
},
// Ações
{
nome: 'acoes.listar',
recurso: 'acoes',
acao: 'listar',
descricao: 'Listar ações'
},
{
nome: 'acoes.criar',
recurso: 'acoes',
acao: 'criar',
descricao: 'Criar novas ações'
},
{
nome: 'acoes.editar',
recurso: 'acoes',
acao: 'editar',
descricao: 'Editar ações'
},
{
nome: 'acoes.excluir',
recurso: 'acoes',
acao: 'excluir',
descricao: 'Excluir ações'
},
// Configuração Compras
{
nome: 'config.compras.gerenciar',
recurso: 'config',
acao: 'gerenciar_compras',
descricao: 'Gerenciar configurações de compras'
}
]
} as const;

View File

@@ -0,0 +1,69 @@
import { mutation, query } from './_generated/server';
import { v } from 'convex/values';
import { getCurrentUserFunction } from './auth';
export const list = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query('produtos').collect();
}
});
export const search = query({
args: { query: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query('produtos')
.withSearchIndex('search_nome', (q) => q.search('nome', args.query))
.take(10);
}
});
export const create = mutation({
args: {
nome: v.string(),
valorEstimado: v.string(),
tipo: v.union(v.literal('servico'), v.literal('estrutura'), v.literal('insumo'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
return await ctx.db.insert('produtos', {
...args,
criadoPor: user._id,
criadoEm: Date.now()
});
}
});
export const update = mutation({
args: {
id: v.id('produtos'),
nome: v.string(),
valorEstimado: v.string(),
tipo: v.union(v.literal('servico'), v.literal('estrutura'), v.literal('insumo'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
await ctx.db.patch(args.id, {
nome: args.nome,
valorEstimado: args.valorEstimado,
tipo: args.tipo
});
}
});
export const remove = mutation({
args: {
id: v.id('produtos')
},
handler: async (ctx, args) => {
const user = await getCurrentUserFunction(ctx);
if (!user) throw new Error('Unauthorized');
await ctx.db.delete(args.id);
}
});

File diff suppressed because it is too large Load Diff