Files
musadeq/backend/src/modules/invoices/invoice.controller.ts

138 lines
4.7 KiB
TypeScript

/**
* ════════════════════════════════════════════════════════════
* مُصادَق (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;
}
}