import { Body, Controller, Param, Post, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../../../common/auth/auth.guard';
import { OrganizationContextGuard } from '../../../common/auth/organization-context.guard';
import { LocationContextGuard, type LocationScopedRequest } from '../../../common/auth/location-context.guard';
import { PermissionGuard } from '../../../common/auth/permission.guard';
import { RequirePermissions } from '../../../common/auth/permissions.decorator';
import { FiscalService } from '../application/fiscal.service';
import { FiscalReconciliationWorker } from '../workers/fiscal-reconciliation.worker';
import { FiscalExportService } from '../exports/fiscal-export.service';
import { ZodValidationPipe } from '../../../common/validation/zod-validation.pipe';
import { fiscalCompletedSchema,fiscalCreateTransactionSchema,fiscalExportCreateSchema,fiscalPeriodSchema,fiscalReceiptSchema,fiscalStartedSchema,fiscalUncertainSchema } from '../../../common/validation/critical-write.schemas';
import type { z } from 'zod';
@Controller('/fiscal')
@UseGuards(AuthGuard, OrganizationContextGuard, LocationContextGuard, PermissionGuard)
export class FiscalController {
constructor(
private readonly fiscal: FiscalService,
private readonly reconciliation: FiscalReconciliationWorker,
private readonly exports: FiscalExportService,
) {}
@Post('/transactions')
@RequirePermissions('fiscal.manage')
create(@Req() req: LocationScopedRequest, @Body(new ZodValidationPipe(fiscalCreateTransactionSchema)) body:z.infer<typeof fiscalCreateTransactionSchema>) {
return this.fiscal.createTransaction({
organizationId: req.organizationId!,
locationId: req.locationId!,
fiscalDeviceId: body.fiscalDeviceId,
checkId: body.checkId,
paymentId: body.paymentId,
transactionType: body.transactionType,
clientTransactionRef: body.clientTransactionRef,
processData: body.processData ?? {},
});
}
@Post('/transactions/:id/started')
@RequirePermissions('fiscal.manage')
started(@Req() req: LocationScopedRequest, @Param('id') id: string, @Body(new ZodValidationPipe(fiscalStartedSchema)) body:z.infer<typeof fiscalStartedSchema>) {
return this.fiscal.markStarted({
organizationId: req.organizationId!,
fiscalTransactionId: id,
providerReference: body.providerReference,
tseTransactionNumber: body.tseTransactionNumber,
tseStartAt: body.tseStartAt,
});
}
@Post('/transactions/:id/completed')
@RequirePermissions('fiscal.manage')
completed(@Req() req: LocationScopedRequest, @Param('id') id: string, @Body(new ZodValidationPipe(fiscalCompletedSchema)) body:z.infer<typeof fiscalCompletedSchema>) {
return this.fiscal.markCompleted({
organizationId: req.organizationId!,
fiscalTransactionId: id,
tseTransactionNumber: body.tseTransactionNumber,
signatureCounter: body.signatureCounter,
tseStartAt: body.tseStartAt,
tseEndAt: body.tseEndAt,
verificationValue: body.verificationValue,
providerReference: body.providerReference,
});
}
@Post('/transactions/:id/uncertain')
@RequirePermissions('fiscal.reconcile')
async uncertain(@Req() req: LocationScopedRequest, @Param('id') id: string, @Body(new ZodValidationPipe(fiscalUncertainSchema)) body:z.infer<typeof fiscalUncertainSchema>) {
const result = await this.fiscal.markUncertain({
organizationId: req.organizationId!,
fiscalTransactionId: id,
providerStatus: body.providerStatus,
});
await this.reconciliation.enqueue(req.organizationId!, id);
return result;
}
@Post('/transactions/:id/receipt')
@RequirePermissions('fiscal.manage')
receipt(@Req() req: LocationScopedRequest, @Param('id') id: string, @Body(new ZodValidationPipe(fiscalReceiptSchema)) body:z.infer<typeof fiscalReceiptSchema>) {
return this.fiscal.createReceiptSnapshot({
organizationId: req.organizationId!,
fiscalTransactionId: id,
receiptNumber: body.receiptNumber,
issuerName: body.issuerName,
issuerAddress: body.issuerAddress,
locale: body.locale ?? req.auth?.locale ?? 'de-DE',
qrPayload: body.qrPayload,
receiptPayload: body.receiptPayload ?? {},
});
}
@Post('/reconciliation/run')
@RequirePermissions('fiscal.reconcile')
runReconciliation() {
return this.reconciliation.runBatch();
}
@Post('/exports')
@RequirePermissions('fiscal.read')
createExport(@Req() req: LocationScopedRequest, @Body(new ZodValidationPipe(fiscalExportCreateSchema)) body:z.infer<typeof fiscalExportCreateSchema>) {
return this.exports.createJob({
organizationId: req.organizationId!,
locationId: req.locationId!,
exportType: body.exportType,
periodStart: body.periodStart,
periodEnd: body.periodEnd,
userId: req.auth?.userId,
});
}
@Post('/exports/preview/cash-journal')
@RequirePermissions('fiscal.read')
previewCashJournal(@Req() req: LocationScopedRequest, @Body(new ZodValidationPipe(fiscalPeriodSchema)) body:z.infer<typeof fiscalPeriodSchema>) {
return this.exports.buildCashJournal({
organizationId: req.organizationId!,
locationId: req.locationId!,
periodStart: body.periodStart,
periodEnd: body.periodEnd,
});
}
@Post('/exports/preview/dsfinvk')
@RequirePermissions('fiscal.read')
previewDsfinvk(@Req() req: LocationScopedRequest, @Body(new ZodValidationPipe(fiscalPeriodSchema)) body:z.infer<typeof fiscalPeriodSchema>) {
return this.exports.buildDsfinvkBoundary({
organizationId: req.organizationId!,
locationId: req.locationId!,
periodStart: body.periodStart,
periodEnd: body.periodEnd,
});
}
}
