import { Body, Controller, Get, Param, Patch, Post, Query, StreamableFile, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { memoryStorage } from 'multer'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { CurrentUser } from '../auth/current-user.decorator'; import { OrgModuleGuard } from '../billing/org-module.guard'; import { RequireOrgModule } from '../billing/org-module.decorator'; import { TreasuryAccessGuard } from './treasury-access.guard'; import type { Actor } from '../common/record-scope'; import { TreasuryOpsService } from './treasury-ops.service'; import { TreasuryReceivablesService } from './treasury-receivables.service'; import { TreasuryService } from './treasury.service'; @Controller('treasury') @UseGuards(JwtAuthGuard, OrgModuleGuard, TreasuryAccessGuard) @RequireOrgModule('treasury') export class TreasuryController { constructor( private readonly treasury: TreasuryService, private readonly receivables: TreasuryReceivablesService, private readonly ops: TreasuryOpsService, ) {} @Get('dashboard') dashboard( @CurrentUser() user: Actor, @Query('from') from?: string, @Query('to') to?: string, ) { return this.treasury.dashboard(user, { from, to }); } @Get('categories') categories(@CurrentUser() user: Actor, @Query('kind') kind?: string) { return this.treasury.listCategories(user, kind); } @Post('categories') createCategory( @CurrentUser() user: Actor, @Body() body: { name: string; kind: 'income' | 'expense' }, ) { return this.treasury.createCategory(user, body); } @Get('accounts') accounts( @CurrentUser() user: Actor, @Query('from') from?: string, @Query('to') to?: string, ) { return this.treasury.listAccounts(user, { from, to }); } @Post('accounts') createAccount(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.treasury.createAccount(user, body); } @Patch('accounts/:id') updateAccount( @CurrentUser() user: Actor, @Param('id') id: string, @Body() body: { name?: string; notes?: string | null; isActive?: boolean; responsibleName?: string | null }, ) { return this.treasury.updateAccount(user, id, body); } @Get('accounts/:id/ledger') ledger( @CurrentUser() user: Actor, @Param('id') id: string, @Query('from') from?: string, @Query('to') to?: string, ) { return this.treasury.ledger(user, id, { from, to }); } @Post('transfers') transfer(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.treasury.transfer(user, body); } @Get('documents') documents( @CurrentUser() user: Actor, @Query('kind') kind: 'income' | 'expense', @Query('q') q?: string, ) { return this.treasury.listDocuments(user, kind === 'expense' ? 'expense' : 'income', q); } @Post('documents') createDocument(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.treasury.createDocument(user, body); } @Post('documents/:id/attachments') @UseInterceptors(FileInterceptor('file', { storage: memoryStorage(), limits: { fileSize: 8_000_000 } })) addAttachment( @CurrentUser() user: Actor, @Param('id') id: string, @UploadedFile() file: { originalname: string; mimetype: string; buffer: Buffer }, ) { return this.treasury.addAttachment(user, id, file); } @Post('documents/:id/settle') settle( @CurrentUser() user: Actor, @Param('id') id: string, @Body() body: { amount: string | number; accountId: string; occurredAt?: string; reference?: string | null }, ) { return this.treasury.settleDocument(user, id, body); } @Get('receivables') receivablesList(@CurrentUser() user: Actor) { return this.receivables.list(user); } @Post('receivables') createReceivable( @CurrentUser() user: Actor, @Body() body: Parameters[1], ) { return this.receivables.create(user, body); } @Post('installments/:id/collect') collectInstallment( @CurrentUser() user: Actor, @Param('id') id: string, @Body() body: Parameters[2], ) { return this.receivables.collect(user, id, body); } @Get('payroll') payroll(@CurrentUser() user: Actor) { return this.ops.listPayroll(user); } @Post('payroll') createPayroll(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.ops.createPayroll(user, body); } @Post('payroll/:id/pay') payPayroll( @CurrentUser() user: Actor, @Param('id') id: string, @Body() body: Parameters[2], ) { return this.ops.payPayroll(user, id, body); } @Get('recurring') recurring(@CurrentUser() user: Actor) { return this.ops.listRecurring(user); } @Post('recurring') createRecurring( @CurrentUser() user: Actor, @Body() body: Parameters[1], ) { return this.ops.createRecurring(user, body); } @Get('reports') report( @CurrentUser() user: Actor, @Query('from') from?: string, @Query('to') to?: string, ) { return this.ops.report(user, from, to); } @Get('reports/export') async exportReport( @CurrentUser() user: Actor, @Query('from') from?: string, @Query('to') to?: string, ) { const buffer = await this.ops.reportExcel(user, from, to); return new StreamableFile(buffer, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: 'attachment; filename="treasury-report.xlsx"', }); } @Post('reconciliations') reconcile(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.ops.reconcile(user, body); } @Post('adjustments') adjust(@CurrentUser() user: Actor, @Body() body: Parameters[1]) { return this.ops.adjust(user, body); } @Post('documents/:id/void') voidDoc( @CurrentUser() user: Actor, @Param('id') id: string, @Body() body: { reason?: string }, ) { return this.treasury.voidDocument(user, id, body.reason); } }