This commit is contained in:
2026-07-22 13:58:38 -03:00
parent 4be043edf3
commit 14a36b68b5
10 changed files with 676 additions and 178 deletions

View File

@@ -1,5 +1,5 @@
import { FastifyRequest, FastifyReply } from 'fastify';
import { createOrderSchema, addItemOrderSchema, updateItemStatusSchema, updateOrderSchema, payOrderSchema } from '../schemas/order.schema';
import { createOrderSchema, addItemOrderSchema, updateItemStatusSchema, updateItemObservacaoSchema, updateOrderSchema, payOrderSchema, partialPaymentSchema } from '../schemas/order.schema';
import * as orderService from '../services/order.service';
import { z } from 'zod';
@@ -23,6 +23,23 @@ export async function listOrdersController(request: FastifyRequest, reply: Fasti
return reply.send(orders);
}
export async function listPaymentsController(request: FastifyRequest, reply: FastifyReply) {
const querySchema = z.object({
dataInicio: z.string().optional(),
dataFim: z.string().optional(),
});
const query = querySchema.parse(request.query);
const filter: any = {};
if (query.dataInicio) filter.dataInicio = query.dataInicio;
if (query.dataFim) filter.dataFim = query.dataFim;
const payments = await orderService.listPayments(
(filter.dataInicio || filter.dataFim) ? filter : undefined
);
return reply.send(payments);
}
export async function createOrderController(request: FastifyRequest, reply: FastifyReply) {
const data = createOrderSchema.parse(request.body);
const order = await orderService.createOrder(data);
@@ -59,6 +76,17 @@ export async function updateItemStatusController(request: FastifyRequest, reply:
return reply.send(item);
}
export async function updateItemObservacaoController(request: FastifyRequest, reply: FastifyReply) {
const { id } = z.object({ id: z.string().uuid() }).parse(request.params);
const data = updateItemObservacaoSchema.parse(request.body);
try {
const item = await orderService.updateItemObservacao(id, data);
return reply.send(item);
} catch (error: any) {
return reply.status(400).send({ error: error.message });
}
}
export async function payOrderController(request: FastifyRequest, reply: FastifyReply) {
const { id } = z.object({ id: z.string().uuid() }).parse(request.params);
const body = request.body && Object.keys(request.body as object).length > 0
@@ -72,6 +100,17 @@ export async function payOrderController(request: FastifyRequest, reply: Fastify
}
}
export async function registerPaymentController(request: FastifyRequest, reply: FastifyReply) {
const { id } = z.object({ id: z.string().uuid() }).parse(request.params);
const data = partialPaymentSchema.parse(request.body);
try {
const pagamento = await orderService.registerPayment(id, data);
return reply.status(201).send(pagamento);
} catch (error: any) {
return reply.status(400).send({ error: error.message });
}
}
export async function updateOrderController(request: FastifyRequest, reply: FastifyReply) {
const { id } = z.object({ id: z.string().uuid() }).parse(request.params);
const data = updateOrderSchema.parse(request.body);

View File

@@ -6,6 +6,9 @@ export async function orderRoutes(app: FastifyInstance) {
// Listagem geral (qualquer autenticado)
app.get('/comandas', { preHandler: [authenticate] }, orderController.listOrdersController);
// Listagem de pagamentos (qualquer autenticado)
app.get('/pagamentos', { preHandler: [authenticate] }, orderController.listPaymentsController);
// Comandas (Garçom, Caixa, Admin)
app.post('/comandas', { preHandler: [requireRoles(['GARCOM', 'CAIXA', 'ADMIN'])] }, orderController.createOrderController);
app.post('/comandas/:id/itens', { preHandler: [requireRoles(['GARCOM', 'CAIXA', 'ADMIN'])] }, orderController.addItemToOrderController);
@@ -13,11 +16,15 @@ export async function orderRoutes(app: FastifyInstance) {
// Pagamento (Caixa, Admin)
app.post('/comandas/:id/pagar', { preHandler: [requireRoles(['CAIXA', 'ADMIN'])] }, orderController.payOrderController);
app.post('/comandas/:id/pagamentos', { preHandler: [requireRoles(['CAIXA', 'ADMIN'])] }, orderController.registerPaymentController);
// Painel Cozinha / Bar
app.get('/painel/itens', { preHandler: [requireRoles(['COZINHA', 'BARMAN', 'ADMIN'])] }, orderController.listKitchenBarController);
app.patch('/painel/itens/:id/status', { preHandler: [requireRoles(['COZINHA', 'BARMAN', 'ADMIN'])] }, orderController.updateItemStatusController);
// Editar observação de item (Garçom, Caixa, Admin)
app.patch('/comandas/itens/:id/observacao', { preHandler: [requireRoles(['GARCOM', 'CAIXA', 'ADMIN'])] }, orderController.updateItemObservacaoController);
// Excluir item de comanda (apenas CAIXA e ADMIN)
app.delete('/comandas/itens/:id', { preHandler: [requireRoles(['CAIXA', 'ADMIN'])] }, orderController.deleteItemFromOrderController);
}

View File

@@ -29,9 +29,25 @@ export const payOrderSchema = z.object({
formaPagamento: z.enum(['DINHEIRO', 'CARTAO_CREDITO', 'CARTAO_DEBITO', 'PIX']),
});
export const partialPaymentSchema = z.object({
valor: z.number().min(0).optional(),
formaPagamento: z.enum(['DINHEIRO', 'CARTAO_CREDITO', 'CARTAO_DEBITO', 'PIX']),
observacao: z.string().optional(),
itemIds: z.array(z.string().uuid()).optional(),
}).refine(
(data) => (data.itemIds && data.itemIds.length > 0) || (data.valor !== undefined && data.valor > 0),
{ message: 'Informe um valor positivo ou selecione itens para pagamento' }
);
export type CreateOrderInput = z.infer<typeof createOrderSchema>;
export type AddItemOrderInput = z.infer<typeof addItemOrderSchema>;
export const updateItemObservacaoSchema = z.object({
observacao: z.string().optional(),
});
export type UpdateItemStatusInput = z.infer<typeof updateItemStatusSchema>;
export type UpdateItemObservacaoInput = z.infer<typeof updateItemObservacaoSchema>;
export type UpdateOrderInput = z.infer<typeof updateOrderSchema>;
export type PayOrderInput = z.infer<typeof payOrderSchema>;
export type PartialPaymentInput = z.infer<typeof partialPaymentSchema>;

View File

@@ -1,5 +1,5 @@
import { prisma } from '../lib/prisma';
import { CreateOrderInput, AddItemOrderInput, UpdateItemStatusInput, UpdateOrderInput, PayOrderInput } from '../schemas/order.schema';
import { CreateOrderInput, AddItemOrderInput, UpdateItemStatusInput, UpdateItemObservacaoInput, UpdateOrderInput, PayOrderInput, PartialPaymentInput } from '../schemas/order.schema';
export interface ListOrdersFilter {
statuses?: string[];
@@ -7,6 +7,42 @@ export interface ListOrdersFilter {
dataFim?: string;
}
export interface ListPaymentsFilter {
dataInicio?: string;
dataFim?: string;
}
export async function listPayments(filter?: ListPaymentsFilter) {
const where: any = {};
if (filter?.dataInicio || filter?.dataFim) {
where.criadoEm = {};
if (filter.dataInicio) {
const [y, m, d] = filter.dataInicio.split('-').map(Number);
where.criadoEm.gte = new Date(y, m - 1, d, 0, 0, 0, 0);
}
if (filter.dataFim) {
const [y, m, d] = filter.dataFim.split('-').map(Number);
where.criadoEm.lte = new Date(y, m - 1, d, 23, 59, 59, 999);
}
}
return prisma.pagamento.findMany({
where,
orderBy: { criadoEm: 'desc' },
include: {
comanda: {
select: {
id: true,
identificador: true,
nomeCliente: true,
status: true,
},
},
},
});
}
export async function listOrders(filter?: ListOrdersFilter) {
const where: any = {};
@@ -30,7 +66,8 @@ export async function listOrders(filter?: ListOrdersFilter) {
include: {
itens: {
include: { produto: true }
}
},
pagamentos: true,
}
});
}
@@ -116,6 +153,28 @@ export async function updateItemStatus(itemId: string, data: UpdateItemStatusInp
});
}
export async function updateItemObservacao(itemId: string, data: UpdateItemObservacaoInput) {
const item = await prisma.itemPedido.findUnique({
where: { id: itemId },
include: { comanda: true },
});
if (!item) {
throw new Error('Item do pedido não encontrado');
}
if (item.comanda.status !== 'ABERTA') {
throw new Error('Não é possível editar itens de uma comanda fechada/paga/cancelada');
}
return prisma.itemPedido.update({
where: { id: itemId },
data: {
observacao: data.observacao ?? null,
},
});
}
export async function payOrder(comandaId: string, data?: PayOrderInput) {
const comanda = await prisma.comanda.findUnique({
where: { id: comandaId },
@@ -128,15 +187,110 @@ export async function payOrder(comandaId: string, data?: PayOrderInput) {
if (comanda.status !== 'ABERTA') {
throw new Error('Comanda não está aberta para pagamento');
}
return prisma.comanda.update({
const totalFinal = calcularTotalFinal(comanda);
const restante = Math.max(totalFinal - comanda.valorPago, 0);
if (restante <= 0) {
throw new Error('Comanda já foi paga integralmente');
}
const [, comandaAtualizada] = await prisma.$transaction([
prisma.pagamento.create({
data: {
comandaId,
valor: restante,
formaPagamento: data?.formaPagamento ?? 'DINHEIRO',
observacao: 'Pagamento integral',
},
}),
prisma.comanda.update({
where: { id: comandaId },
data: {
status: 'PAGA',
fechadoEm: new Date(),
formaPagamento: data?.formaPagamento,
valorPago: totalFinal,
},
}),
]);
return comandaAtualizada;
}
function calcularTotalFinal(comanda: { total: number; desconto: number; acrescimo: number; taxaServico: boolean }): number {
let total = comanda.total || 0;
total -= (comanda.desconto || 0);
total += (comanda.acrescimo || 0);
if (comanda.taxaServico) total += total * 0.10;
return Math.max(total, 0);
}
export async function registerPayment(comandaId: string, data: PartialPaymentInput) {
const comanda = await prisma.comanda.findUnique({
where: { id: comandaId },
data: {
status: 'PAGA',
fechadoEm: new Date(),
formaPagamento: data?.formaPagamento,
},
include: { itens: true },
});
if (!comanda) {
throw new Error('Comanda não encontrada');
}
if (comanda.status === 'PAGA') {
throw new Error('Comanda já foi paga integralmente');
}
if (comanda.status === 'CANCELADA') {
throw new Error('Não é possível pagar uma comanda cancelada');
}
const totalFinal = calcularTotalFinal(comanda);
const restante = Math.max(totalFinal - comanda.valorPago, 0);
if (restante <= 0) {
throw new Error('Comanda já foi paga integralmente');
}
let valorPagamento = data.valor ?? 0;
if (data.itemIds && data.itemIds.length > 0) {
const itensSelecionados = comanda.itens.filter(i => data.itemIds!.includes(i.id));
if (itensSelecionados.length === 0) {
throw new Error('Nenhum item válido selecionado');
}
valorPagamento = itensSelecionados.reduce((acc, i) => acc + i.precoUnitario * i.quantidade, 0);
}
if (!valorPagamento || valorPagamento <= 0) {
throw new Error('Valor do pagamento deve ser maior que zero');
}
if (valorPagamento > restante) {
throw new Error(`Valor excede o restante. Disponível: R$ ${restante.toFixed(2)}`);
}
const novoValorPago = comanda.valorPago + valorPagamento;
const totalmentePago = novoValorPago >= totalFinal;
const pagamento = await prisma.$transaction([
prisma.pagamento.create({
data: {
comandaId,
valor: valorPagamento,
formaPagamento: data.formaPagamento,
observacao: data.observacao ?? null,
},
}),
prisma.comanda.update({
where: { id: comandaId },
data: {
valorPago: novoValorPago,
...(totalmentePago ? { status: 'PAGA', fechadoEm: new Date() } : {}),
},
}),
]);
return pagamento[0];
}
export async function updateOrder(comandaId: string, data: UpdateOrderInput) {
@@ -168,7 +322,8 @@ export async function updateOrder(comandaId: string, data: UpdateOrderInput) {
include: {
itens: {
include: { produto: true }
}
},
pagamentos: true,
}
});
}