Etapa 6 — Vendas, parcelas e dashboards

Adiciona o módulo sales: vendas com itens/parcelas em transação, transições
de status controladas (orçamento→confirmada→concluída, cancelamento em
cascata pras parcelas), edição só em orçamento, recebimentos (marcar-pago,
atualizar-atrasadas) e dashboards semanal/mensal/financeiro do tenant.

Corrige uma falha de isolamento entre tenants nas views de dashboard
(rodavam com privilégio do dono, ignorando RLS) via
security_invoker = true nas views, coberta por teste e2e dedicado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 22:07:00 -03:00
co-authored by Claude Sonnet 5
parent 24b3bb9ae4
commit 7daaa16935
19 changed files with 1891 additions and 9 deletions
@@ -0,0 +1,46 @@
import {
Body,
Controller,
Get,
Param,
ParseUUIDPipe,
Patch,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { TenantGuard } from '../auth/guards/tenant.guard';
import { JwtPayload } from '../auth/types/jwt-payload.type';
import { ListRecebimentosQueryDto } from './dto/list-recebimentos.query.dto';
import { MarcarPagoDto } from './dto/marcar-pago.dto';
import { SalePaymentsService } from './sale-payments.service';
@UseGuards(JwtAuthGuard, TenantGuard)
@Controller('tenant/recebimentos')
export class SalePaymentsController {
constructor(private readonly salePaymentsService: SalePaymentsService) {}
@Get()
findAll(@Query() query: ListRecebimentosQueryDto) {
return this.salePaymentsService.findAll(query);
}
@Patch(':id/marcar-pago')
marcarPago(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: MarcarPagoDto,
@CurrentUser() user: JwtPayload,
@Req() req: Request,
) {
return this.salePaymentsService.marcarPago(id, dto, user, req.ip);
}
@Post('atualizar-atrasadas')
atualizarAtrasadas(@CurrentUser() user: JwtPayload, @Req() req: Request) {
return this.salePaymentsService.atualizarAtrasadas(user, req.ip);
}
}