feat: add internal finance ledger for accounts, claims, and payroll
Keep booked income and expense separate from cash balances, with installments, recurring items, and reports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query, StreamableFile, UseGuards } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import { Roles } from '../auth/roles.decorator';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
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, RolesGuard)
|
||||
@Roles('owner', 'admin')
|
||||
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<TreasuryService['createAccount']>[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<TreasuryService['transfer']>[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<TreasuryService['createDocument']>[1]) {
|
||||
return this.treasury.createDocument(user, body);
|
||||
}
|
||||
|
||||
@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<TreasuryReceivablesService['create']>[1],
|
||||
) {
|
||||
return this.receivables.create(user, body);
|
||||
}
|
||||
|
||||
@Post('installments/:id/collect')
|
||||
collectInstallment(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('id') id: string,
|
||||
@Body() body: Parameters<TreasuryReceivablesService['collect']>[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<TreasuryOpsService['createPayroll']>[1]) {
|
||||
return this.ops.createPayroll(user, body);
|
||||
}
|
||||
|
||||
@Post('payroll/:id/pay')
|
||||
payPayroll(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('id') id: string,
|
||||
@Body() body: Parameters<TreasuryOpsService['payPayroll']>[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<TreasuryOpsService['createRecurring']>[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<TreasuryOpsService['reconcile']>[1]) {
|
||||
return this.ops.reconcile(user, body);
|
||||
}
|
||||
|
||||
@Post('adjustments')
|
||||
adjust(@CurrentUser() user: Actor, @Body() body: Parameters<TreasuryOpsService['adjust']>[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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user