- Backend: Fastify + Prisma/SQLite com autenticacao JWT - CRUD completo: usuarios, categorias, produtos, comandas - Sistema de comandas com itens, status, pagamento - Controle de estoque (entrada/saida automatica) - Relatorios e dashboard com KPIs - Notificacoes sonoras na cozinha - Frontend: SPA vanilla TS com tema dark glassmorphism - Mobile-first com bottom-sheet e cards compactos - Busca de comandas por periodo - Modal de detalhes com secao editavel colapsavel
138 lines
4.5 KiB
TypeScript
138 lines
4.5 KiB
TypeScript
import { prisma } from '../lib/prisma';
|
|
|
|
export async function getSalesReport(inicio: Date, fim: Date) {
|
|
const comandas = await prisma.comanda.findMany({
|
|
where: {
|
|
status: 'PAGA',
|
|
fechadoEm: { gte: inicio, lte: fim },
|
|
},
|
|
include: {
|
|
itens: {
|
|
include: { produto: true },
|
|
},
|
|
},
|
|
orderBy: { fechadoEm: 'desc' },
|
|
});
|
|
|
|
const totalVendas = comandas.reduce((acc, c) => acc + c.total, 0);
|
|
const totalDescontos = comandas.reduce((acc, c) => acc + c.desconto, 0);
|
|
const totalAcrescimos = comandas.reduce((acc, c) => acc + c.acrescimo, 0);
|
|
const totalComandas = comandas.length;
|
|
|
|
// Produtos mais vendidos
|
|
const produtoVendas: Record<string, { nome: string; quantidade: number; total: number }> = {};
|
|
for (const c of comandas) {
|
|
for (const item of c.itens) {
|
|
const key = item.produtoId;
|
|
if (!produtoVendas[key]) {
|
|
produtoVendas[key] = { nome: item.produto.nome, quantidade: 0, total: 0 };
|
|
}
|
|
produtoVendas[key].quantidade += item.quantidade;
|
|
produtoVendas[key].total += item.precoUnitario * item.quantidade;
|
|
}
|
|
}
|
|
|
|
const topProdutos = Object.values(produtoVendas)
|
|
.sort((a, b) => b.quantidade - a.quantidade)
|
|
.slice(0, 10);
|
|
|
|
// Vendas por dia
|
|
const vendasPorDia: Record<string, { vendas: number; comandas: number }> = {};
|
|
for (const c of comandas) {
|
|
const dia = c.fechadoEm!.toISOString().split('T')[0];
|
|
if (!vendasPorDia[dia]) vendasPorDia[dia] = { vendas: 0, comandas: 0 };
|
|
vendasPorDia[dia].vendas += c.total;
|
|
vendasPorDia[dia].comandas += 1;
|
|
}
|
|
|
|
// Vendas por forma de pagamento
|
|
const vendasPorPagamento: Record<string, number> = {};
|
|
for (const c of comandas) {
|
|
const fp = c.formaPagamento || 'NAO_INFORMADO';
|
|
vendasPorPagamento[fp] = (vendasPorPagamento[fp] || 0) + c.total;
|
|
}
|
|
|
|
return {
|
|
periodo: { inicio, fim },
|
|
resumo: {
|
|
totalVendas,
|
|
totalDescontos,
|
|
totalAcrescimos,
|
|
totalComandas,
|
|
ticketMedio: totalComandas > 0 ? totalVendas / totalComandas : 0,
|
|
},
|
|
topProdutos,
|
|
vendasPorDia: Object.entries(vendasPorDia).map(([data, v]) => ({ data, ...v })),
|
|
vendasPorPagamento: Object.entries(vendasPorPagamento).map(([forma, total]) => ({ forma, total })),
|
|
comandas: comandas.map(c => ({
|
|
id: c.id,
|
|
identificador: c.identificador,
|
|
nomeCliente: c.nomeCliente,
|
|
total: c.total,
|
|
desconto: c.desconto,
|
|
acrescimo: c.acrescimo,
|
|
formaPagamento: c.formaPagamento,
|
|
criadoEm: c.criadoEm,
|
|
fechadoEm: c.fechadoEm,
|
|
itensCount: c.itens.length,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export async function getDashboardData() {
|
|
const now = new Date();
|
|
const inicioDia = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const inicioSemana = new Date(now);
|
|
inicioSemana.setDate(now.getDate() - now.getDay());
|
|
inicioSemana.setHours(0, 0, 0, 0);
|
|
const inicioMes = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
|
const [comandasHoje, comandasSemana, comandasMes, totalProdutos, produtosEstoqueBaixo, comandasAbertas] = await Promise.all([
|
|
prisma.comanda.findMany({
|
|
where: { status: 'PAGA', fechadoEm: { gte: inicioDia } },
|
|
include: { itens: true },
|
|
}),
|
|
prisma.comanda.findMany({
|
|
where: { status: 'PAGA', fechadoEm: { gte: inicioSemana } },
|
|
include: { itens: true },
|
|
}),
|
|
prisma.comanda.findMany({
|
|
where: { status: 'PAGA', fechadoEm: { gte: inicioMes } },
|
|
include: { itens: true },
|
|
}),
|
|
prisma.produto.count({ where: { ativo: true } }),
|
|
prisma.produto.findMany({
|
|
where: { ativo: true },
|
|
include: { categoria: true },
|
|
}),
|
|
prisma.comanda.count({ where: { status: 'ABERTA' } }),
|
|
]);
|
|
|
|
const sumTotal = (cs: any[]) => cs.reduce((acc, c) => acc + c.total, 0);
|
|
const sumItens = (cs: any[]) => cs.reduce((acc, c) => acc + c.itens.reduce((a: number, i: any) => a + i.quantidade, 0), 0);
|
|
|
|
const estoqueBaixo = produtosEstoqueBaixo.filter(p => p.estoqueAtual <= p.estoqueMinimo);
|
|
|
|
return {
|
|
vendasHoje: {
|
|
total: sumTotal(comandasHoje),
|
|
comandas: comandasHoje.length,
|
|
itens: sumItens(comandasHoje),
|
|
},
|
|
vendasSemana: {
|
|
total: sumTotal(comandasSemana),
|
|
comandas: comandasSemana.length,
|
|
itens: sumItens(comandasSemana),
|
|
},
|
|
vendasMes: {
|
|
total: totalProdutos,
|
|
comandas: comandasMes.length,
|
|
itens: sumItens(comandasMes),
|
|
totalVendas: sumTotal(comandasMes),
|
|
},
|
|
totalProdutos,
|
|
produtosEstoqueBaixo: estoqueBaixo.length,
|
|
comandasAbertas,
|
|
};
|
|
}
|