/** * ════════════════════════════════════════════════════════════ * مُصادَق (Musadaq) — Invoices Controller / متحكم الفواتير * ════════════════════════════════════════════════════════════ * Handles HTTP requests related to invoice management. * يتعامل مع طلبات HTTP المتعلقة بإدارة الفواتير. * ════════════════════════════════════════════════════════════ */ import { Controller, Get, Post, Patch, Body, Param, UseGuards, UploadedFile, UseInterceptors, ParseUUIDPipe, Res, } from '@nestjs/common'; import { Response } from 'express'; import { FileInterceptor } from '@nestjs/platform-express'; import { InvoicesService } from './invoice.service'; import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { RolesGuard } from '../../common/guards/roles.guard'; import { Roles } from '../../common/decorators/roles.decorator'; import { UserRole } from '../users/enums/role.enum'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; @Controller('invoices') @UseGuards(JwtAuthGuard, RolesGuard) export class InvoicesController { constructor(private invoicesService: InvoicesService) {} /** * قائمة جميع الفواتير للمكتب * List all invoices for the entire accounting office (tenant) */ @Get() async findAllByTenant(@CurrentUser() user: any) { return this.invoicesService.findAllByTenant(user); } /** * رفع فاتورة لشركة محددة * Upload an invoice file for a specific company */ @Post('upload/:companyId') @Roles(UserRole.ADMIN, UserRole.ACCOUNTANT, UserRole.SUPER_ADMIN) @UseInterceptors(FileInterceptor('file')) async upload( @CurrentUser() user: any, @Param('companyId', ParseUUIDPipe) companyId: string, @UploadedFile() file: Express.Multer.File, ) { return this.invoicesService.upload(user, companyId, file); } /** * قائمة الفواتير لشركة محددة * List all invoices for a specific company */ @Get('company/:companyId') async findAll( @CurrentUser() user: any, @Param('companyId', ParseUUIDPipe) companyId: string, ) { return this.invoicesService.findAll(user, companyId); } /** * تفاصيل فاتورة محددة * Get details of a specific invoice */ @Get(':id') async findOne( @CurrentUser() user: any, @Param('id', ParseUUIDPipe) id: string, ) { return this.invoicesService.findOne(user, id); } /** * تحديث بيانات الفاتورة يدوياً */ /** * إرسال الفاتورة إلى بوابة جو فوترة الحكومية * Submit an invoice to the official JoFotara portal */ @Post(':id/submit') @Roles(UserRole.ADMIN, UserRole.ACCOUNTANT, UserRole.SUPER_ADMIN) async submit(@CurrentUser() user: any, @Param('id', ParseUUIDPipe) id: string) { return this.invoicesService.submitToJoFotara(user, id); } /** * حذف الفاتورة نهائياً * Permanently delete an invoice */ @Post(':id/delete') // Using POST for delete to match frontend request style or use standard DELETE @Roles(UserRole.ADMIN, UserRole.ACCOUNTANT, UserRole.SUPER_ADMIN) async remove(@CurrentUser() user: any, @Param('id', ParseUUIDPipe) id: string) { return this.invoicesService.remove(user, id); } /** * الحصول على الملف الأصلي للفاتورة * Download/Stream the original invoice file */ @Get(':id/file') async getFile( @CurrentUser() user: any, @Param('id', ParseUUIDPipe) id: string, @Res({ passthrough: true }) res: Response, ) { const streamableFile = await this.invoicesService.getFile(user, id); // We need to determine the content type to ensure it opens inline in the browser (iframe) // We can fetch the invoice details first to get the extension. const invoice = await this.invoicesService.findOne(user, id); let mimeType = 'application/pdf'; // Default fallback if (invoice.original_file_path) { const ext = invoice.original_file_path.split('.').pop()?.toLowerCase(); if (ext === 'jpg' || ext === 'jpeg') mimeType = 'image/jpeg'; else if (ext === 'png') mimeType = 'image/png'; } res.set({ 'Content-Type': mimeType, 'Content-Disposition': 'inline', // This forces the browser to display it instead of downloading }); return streamableFile; } }