From 842a862b873dbe797e2b59ee5d5c10deffb010fe Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 6 Apr 2025 21:13:46 +0200 Subject: [PATCH] refactor(nestjs): attachments module --- packages/server-nest/package.json | 4 + .../src/common/constants/files.constants.ts | 1 + .../src/common/constants/multer.constants.ts | 24 +++ .../src/common/constants/multer.utils.ts | 38 ++++ .../common/interceptors/file.interceptor.ts | 70 ++++++ .../modules/Attachments/Attachment.module.ts | 51 ++++- .../Attachments/Attachments.controller.ts | 202 ++++++++++++++++++ .../Attachments/AttachmentsApplication.ts | 4 - .../modules/Attachments/DeleteAttachment.ts | 20 +- .../src/modules/Attachments/GetAttachment.ts | 8 +- .../Attachments/GetAttachmentPresignedUrl.ts | 25 ++- .../src/modules/Attachments/LinkAttachment.ts | 67 +++--- .../modules/Attachments/S3UploadPipeline.ts | 53 ++--- .../modules/Attachments/UnlinkAttachment.ts | 71 +++--- .../Attachments/ValidateAttachments.ts | 17 +- .../src/modules/Attachments/_utils.ts | 2 +- .../decorators/InjectAttachable.decorator.ts | 23 ++ .../Attachments/dtos/Attachment.dto.ts | 25 +++ .../Attachments/events/AttachmentsOnBills.ts | 100 +++------ .../events/AttachmentsOnCreditNote.ts | 90 +++----- .../events/AttachmentsOnExpenses.ts | 85 +++----- .../events/AttachmentsOnManualJournals.ts | 92 +++----- .../events/AttachmentsOnPaymentsMade.ts | 90 +++----- .../events/AttachmentsOnPaymentsReceived.ts | 88 +++----- .../events/AttachmentsOnSaleEstimates.ts | 89 +++----- .../events/AttachmentsOnSaleInvoice.ts | 90 +++----- .../events/AttachmentsOnSaleReceipts.ts | 90 +++----- .../events/AttachmentsOnVendorCredits.ts | 90 +++----- .../Attachments/models/DocumentLink.model.ts | 2 + .../src/modules/Attachments/utils.ts | 11 +- .../ChromiumlyTenancy/models/DocumentLink.ts | 2 +- .../CommandCreditNoteDTOTransform.service.ts | 5 - .../CreditNotes/types/CreditNotes.types.ts | 40 +--- .../src/modules/Expenses/Expenses.types.ts | 41 +--- .../server-nest/src/modules/S3/S3.module.ts | 1 + .../SaleInvoices/dtos/SaleInvoice.dto.ts | 16 ++ .../SaleInvoices/models/SaleInvoice.ts | 2 + .../commands/CreateSaleReceipt.service.ts | 1 - .../SaleReceipts/types/SaleReceipts.types.ts | 29 +-- .../services/Attachments/S3UploadPipeline.ts | 3 +- pnpm-lock.yaml | 15 +- 41 files changed, 918 insertions(+), 859 deletions(-) create mode 100644 packages/server-nest/src/common/constants/files.constants.ts create mode 100644 packages/server-nest/src/common/constants/multer.constants.ts create mode 100644 packages/server-nest/src/common/constants/multer.utils.ts create mode 100644 packages/server-nest/src/common/interceptors/file.interceptor.ts create mode 100644 packages/server-nest/src/modules/Attachments/Attachments.controller.ts create mode 100644 packages/server-nest/src/modules/Attachments/decorators/InjectAttachable.decorator.ts diff --git a/packages/server-nest/package.json b/packages/server-nest/package.json index 1ed094b5b..9eb37819a 100644 --- a/packages/server-nest/package.json +++ b/packages/server-nest/package.json @@ -28,6 +28,7 @@ "@casl/ability": "^5.4.3", "@lemonsqueezy/lemonsqueezy.js": "^2.2.0", "@liaoliaots/nestjs-redis": "^10.0.0", + "@types/multer": "^1.4.11", "@nestjs/bull": "^10.2.1", "@nestjs/bullmq": "^10.2.2", "@nestjs/cache-manager": "^2.2.2", @@ -69,11 +70,14 @@ "lodash": "^4.17.21", "lru-cache": "^6.0.0", "mathjs": "^9.4.0", + "mime-types": "^2.1.35", "moment": "^2.30.1", "moment-range": "^4.0.2", "moment-timezone": "^0.5.43", "mysql": "^2.18.1", "mysql2": "^3.11.3", + "multer": "1.4.5-lts.1", + "multer-s3": "^3.0.1", "nestjs-cls": "^5.2.0", "nestjs-i18n": "^10.4.9", "nestjs-redis": "^1.3.3", diff --git a/packages/server-nest/src/common/constants/files.constants.ts b/packages/server-nest/src/common/constants/files.constants.ts new file mode 100644 index 000000000..9f7f763e2 --- /dev/null +++ b/packages/server-nest/src/common/constants/files.constants.ts @@ -0,0 +1 @@ +export const MULTER_MODULE_OPTIONS = 'MULTER_MODULE_OPTIONS'; \ No newline at end of file diff --git a/packages/server-nest/src/common/constants/multer.constants.ts b/packages/server-nest/src/common/constants/multer.constants.ts new file mode 100644 index 000000000..5db769551 --- /dev/null +++ b/packages/server-nest/src/common/constants/multer.constants.ts @@ -0,0 +1,24 @@ +import type { Multer } from 'multer'; +import * as multerS3 from 'multer-s3'; + +export const multerExceptions = { + // from https://github.com/expressjs/multer/blob/master/lib/multer-error.js + LIMIT_PART_COUNT: 'Too many parts', + LIMIT_FILE_SIZE: 'File too large', + LIMIT_FILE_COUNT: 'Too many files', + LIMIT_FIELD_KEY: 'Field name too long', + LIMIT_FIELD_VALUE: 'Field value too long', + LIMIT_FIELD_COUNT: 'Too many fields', + LIMIT_UNEXPECTED_FILE: 'Unexpected field', + MISSING_FIELD_NAME: 'Field name missing', +}; + +export const busboyExceptions = { + // from https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js + MULTIPART_BOUNDARY_NOT_FOUND: 'Multipart: Boundary not found', + MULTIPART_MALFORMED_PART_HEADER: 'Malformed part header', + MULTIPART_UNEXPECTED_END_OF_FORM: 'Unexpected end of form', + MULTIPART_UNEXPECTED_END_OF_FILE: 'Unexpected end of file', +}; + + diff --git a/packages/server-nest/src/common/constants/multer.utils.ts b/packages/server-nest/src/common/constants/multer.utils.ts new file mode 100644 index 000000000..3bd4017df --- /dev/null +++ b/packages/server-nest/src/common/constants/multer.utils.ts @@ -0,0 +1,38 @@ +import { + BadRequestException, + HttpException, + PayloadTooLargeException, +} from '@nestjs/common'; +import { multerExceptions, busboyExceptions } from './multer.constants'; + +// Multer may add in a 'field' property to the error +// https://github.com/expressjs/multer/blob/aa42bea6ac7d0cb8fcb279b15a7278cda805dc63/lib/multer-error.js#L19 +export function transformException( + error: (Error & { field?: string }) | undefined, +) { + if (!error || error instanceof HttpException) { + return error; + } + switch (error.message) { + case multerExceptions.LIMIT_FILE_SIZE: + return new PayloadTooLargeException(error.message); + case multerExceptions.LIMIT_FILE_COUNT: + case multerExceptions.LIMIT_FIELD_KEY: + case multerExceptions.LIMIT_FIELD_VALUE: + case multerExceptions.LIMIT_FIELD_COUNT: + case multerExceptions.LIMIT_UNEXPECTED_FILE: + case multerExceptions.LIMIT_PART_COUNT: + case multerExceptions.MISSING_FIELD_NAME: + if (error.field) { + return new BadRequestException(`${error.message} - ${error.field}`); + } + return new BadRequestException(error.message); + case busboyExceptions.MULTIPART_BOUNDARY_NOT_FOUND: + return new BadRequestException(error.message); + case busboyExceptions.MULTIPART_MALFORMED_PART_HEADER: + case busboyExceptions.MULTIPART_UNEXPECTED_END_OF_FORM: + case busboyExceptions.MULTIPART_UNEXPECTED_END_OF_FILE: + return new BadRequestException(`Multipart: ${error.message}`); + } + return error; +} \ No newline at end of file diff --git a/packages/server-nest/src/common/interceptors/file.interceptor.ts b/packages/server-nest/src/common/interceptors/file.interceptor.ts new file mode 100644 index 000000000..4eb41b003 --- /dev/null +++ b/packages/server-nest/src/common/interceptors/file.interceptor.ts @@ -0,0 +1,70 @@ +import { + CallHandler, + ExecutionContext, + Inject, + mixin, + NestInterceptor, + Optional, + Type, +} from '@nestjs/common'; +import * as multer from 'multer'; +import { Observable } from 'rxjs'; +import { MULTER_MODULE_OPTIONS } from '../constants/files.constants'; +import { transformException } from '../constants/multer.utils'; +import { MulterModuleOptions } from '@nestjs/platform-express'; +import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; + +type MulterInstance = any; +/** + * @param {string} fieldName + * @param {Function|MulterOptions} localOptions - Function that receives controller instance or MulterOptions object + */ +export function FileInterceptor( + fieldName: string, + localOptions?: ((instance: any) => MulterOptions) | MulterOptions, +): Type { + class MixinInterceptor implements NestInterceptor { + protected multer: MulterInstance; + + constructor( + @Optional() + @Inject(MULTER_MODULE_OPTIONS) + options: (() => MulterModuleOptions | MulterModuleOptions) = () => ({}), + ) { + const resolvedOptions = typeof localOptions === 'function' + ? localOptions(this) + : localOptions; + + this.multer = (multer as any)({ + ...(typeof options === 'function' ? options() : options), + ...resolvedOptions, + }); + } + + async intercept( + context: ExecutionContext, + next: CallHandler, + ): Promise> { + const ctx = context.switchToHttp(); + + await new Promise((resolve, reject) => + this.multer.single(fieldName)( + ctx.getRequest(), + ctx.getResponse(), + (err: any) => { + if (err) { + const error = transformException(err); + return reject(error); + } + resolve(); + }, + ), + ); + return next.handle(); + } + } + + const Interceptor = mixin(MixinInterceptor); + + return Interceptor; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Attachments/Attachment.module.ts b/packages/server-nest/src/modules/Attachments/Attachment.module.ts index db8090ecb..dbe8c388f 100644 --- a/packages/server-nest/src/modules/Attachments/Attachment.module.ts +++ b/packages/server-nest/src/modules/Attachments/Attachment.module.ts @@ -1,5 +1,6 @@ import { Module } from "@nestjs/common"; -import { S3Module } from "../S3/S3.module"; +import * as multerS3 from 'multer-s3'; +import { S3_CLIENT, S3Module } from "../S3/S3.module"; import { DeleteAttachment } from "./DeleteAttachment"; import { GetAttachment } from "./GetAttachment"; import { getAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl"; @@ -13,11 +14,26 @@ import { AttachmentsOnExpenses } from "./events/AttachmentsOnExpenses"; import { AttachmentsOnPaymentsReceived } from "./events/AttachmentsOnPaymentsReceived"; import { AttachmentsOnManualJournals } from "./events/AttachmentsOnManualJournals"; import { AttachmentsOnVendorCredits } from "./events/AttachmentsOnVendorCredits"; +import { AttachmentsOnSaleInvoiceCreated } from "./events/AttachmentsOnSaleInvoice"; +import { AttachmentsController } from "./Attachments.controller"; +import { RegisterTenancyModel } from "../Tenancy/TenancyModels/Tenancy.module"; +import { DocumentModel } from "./models/Document.model"; +import { DocumentLinkModel } from "./models/DocumentLink.model"; +import { AttachmentsApplication } from "./AttachmentsApplication"; +import { UploadDocument } from "./UploadDocument"; +import { AttachmentUploadPipeline } from "./S3UploadPipeline"; +import { MULTER_MODULE_OPTIONS } from "@/common/constants/files.constants"; +import { ConfigService } from "@nestjs/config"; +import { S3Client } from "@aws-sdk/client-s3"; - +const models = [ + RegisterTenancyModel(DocumentModel), + RegisterTenancyModel(DocumentLinkModel), +]; @Module({ - imports: [S3Module], + imports: [S3Module, ...models], + controllers: [AttachmentsController], providers: [ DeleteAttachment, GetAttachment, @@ -31,7 +47,34 @@ import { AttachmentsOnVendorCredits } from "./events/AttachmentsOnVendorCredits" AttachmentsOnExpenses, AttachmentsOnPaymentsReceived, AttachmentsOnManualJournals, - AttachmentsOnVendorCredits + AttachmentsOnVendorCredits, + AttachmentsOnSaleInvoiceCreated, + AttachmentsApplication, + UploadDocument, + AttachmentUploadPipeline, + { + provide: MULTER_MODULE_OPTIONS, + inject: [ConfigService, S3_CLIENT], + useFactory: (configService: ConfigService, s3: S3Client) => ({ + storage: multerS3({ + s3, + bucket: configService.get('s3.bucket'), + contentType: multerS3.AUTO_CONTENT_TYPE, + metadata: function (req, file, cb) { + cb(null, { fieldName: file.fieldname }); + }, + key: function (req, file, cb) { + cb(null, Date.now().toString()); + }, + acl: function(req, file, cb) { + // Conditionally set file to public or private based on isPublic flag + const aclValue = true ? 'public-read' : 'private'; + // Set ACL based on the isPublic flag + cb(null, aclValue); + } + }), + }) + } ] }) export class AttachmentsModule {} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Attachments/Attachments.controller.ts b/packages/server-nest/src/modules/Attachments/Attachments.controller.ts new file mode 100644 index 000000000..9bfe5cc3a --- /dev/null +++ b/packages/server-nest/src/modules/Attachments/Attachments.controller.ts @@ -0,0 +1,202 @@ +import mime from 'mime-types'; +import { Response, NextFunction, Request } from 'express'; +import { + ApiBody, + ApiConsumes, + ApiOperation, + ApiParam, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Res, + UnauthorizedException, + UploadedFile, + UseInterceptors, +} from '@nestjs/common'; +import { + LinkAttachmentDto, + UnlinkAttachmentDto, + UploadAttachmentDto, +} from './dtos/Attachment.dto'; +import { AttachmentsApplication } from './AttachmentsApplication'; +import { AttachmentUploadPipeline } from './S3UploadPipeline'; +import { FileInterceptor } from '@/common/interceptors/file.interceptor'; +import { ConfigService } from '@nestjs/config'; + +@ApiTags('Attachments') +@Controller('/attachments') +export class AttachmentsController { + /** + * @param {AttachmentsApplication} attachmentsApplication - Attachments application. + * @param uploadPipelineService + */ + constructor( + private readonly attachmentsApplication: AttachmentsApplication, + private readonly uploadPipelineService: AttachmentUploadPipeline, + private readonly configService: ConfigService, + ) {} + + /** + * Uploads the attachments to S3 and store the file metadata to DB. + */ + @Post() + @UseInterceptors(FileInterceptor('file')) + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Upload attachment to S3' }) + @ApiBody({ description: 'Upload attachment', type: UploadAttachmentDto }) + @ApiResponse({ + status: 200, + description: 'The document has been uploaded successfully', + }) + @ApiResponse({ + status: 401, + description: 'Unauthorized - File upload failed', + }) + async uploadAttachment( + @UploadedFile() file: Express.Multer.File, + res: Response, + next: NextFunction, + ): Promise { + if (!file) { + throw new UnauthorizedException({ + errorType: 'FILE_UPLOAD_FAILED', + message: 'Now file uploaded.', + }); + } + const data = await this.attachmentsApplication.upload(file); + + return res.status(200).send({ + status: 200, + message: 'The document has uploaded successfully.', + data, + }); + } + + /** + * Retrieves the given attachment key. + */ + @Get('/:id') + @ApiOperation({ summary: 'Get attachment by ID' }) + @ApiParam({ name: 'id', description: 'Attachment ID' }) + @ApiResponse({ status: 200, description: 'Returns the attachment file' }) + async getAttachment( + @Res() res: Response, + @Param('id') documentId: string, + ): Promise { + const data = await this.attachmentsApplication.get(documentId); + + const byte = await data.Body.transformToByteArray(); + const extension = mime.extension(data.ContentType); + const buffer = Buffer.from(byte); + + res.set('Content-Disposition', `filename="${documentId}.${extension}"`); + res.set('Content-Type', data.ContentType); + res.send(buffer); + } + + /** + * Deletes the given document key. + */ + @Delete('/:id') + @ApiOperation({ summary: 'Delete attachment by ID' }) + @ApiParam({ name: 'id', description: 'Attachment ID' }) + @ApiResponse({ + status: 200, + description: 'The document has been deleted successfully', + }) + async deleteAttachment( + @Res() res: Response, + @Param('id') documentId: string, + ): Promise { + await this.attachmentsApplication.delete(documentId); + + return res.status(200).send({ + status: 200, + message: 'The document has been delete successfully.', + }); + } + + /** + * Links the given document key. + */ + @Post('/:id/link') + @ApiOperation({ summary: 'Link attachment to a model' }) + @ApiParam({ name: 'id', description: 'Attachment ID' }) + @ApiBody({ type: LinkAttachmentDto }) + @ApiResponse({ + status: 200, + description: 'The document has been linked successfully', + }) + async linkDocument( + @Body() linkDocumentDto: LinkAttachmentDto, + @Param('id') documentId: string, + @Res() res: Response, + ): Promise { + await this.attachmentsApplication.link( + documentId, + linkDocumentDto.modelRef, + linkDocumentDto.modelId, + ); + + return res.status(200).send({ + status: 200, + message: 'The document has been linked successfully.', + }); + } + + /** + * Links the given document key. + */ + @Post('/:id/unlink') + @ApiOperation({ summary: 'Unlink attachment from a model' }) + @ApiParam({ name: 'id', description: 'Attachment ID' }) + @ApiBody({ type: UnlinkAttachmentDto }) + @ApiResponse({ + status: 200, + description: 'The document has been unlinked successfully', + }) + async unlinkDocument( + @Body() unlinkDto: UnlinkAttachmentDto, + @Param('id') documentId: string, + @Res() res: Response, + ): Promise { + await this.attachmentsApplication.link( + documentId, + unlinkDto.modelRef, + unlinkDto.modelId, + ); + + return res.status(200).send({ + status: 200, + message: 'The document has been linked successfully.', + }); + } + + /** + * Retreives the presigned url of the given attachment key. + */ + @Get('/:id/presigned-url') + @ApiOperation({ summary: 'Get presigned URL for attachment' }) + @ApiParam({ name: 'id', description: 'Attachment ID' }) + @ApiResponse({ + status: 200, + description: 'Returns the presigned URL for the attachment', + }) + async getAttachmentPresignedUrl( + @Param('id') documentKey: string, + res: Response, + next: NextFunction, + ): Promise { + const presignedUrl = + await this.attachmentsApplication.getPresignedUrl(documentKey); + + return res.status(200).send({ presignedUrl }); + } +} diff --git a/packages/server-nest/src/modules/Attachments/AttachmentsApplication.ts b/packages/server-nest/src/modules/Attachments/AttachmentsApplication.ts index 043a28922..baee3775e 100644 --- a/packages/server-nest/src/modules/Attachments/AttachmentsApplication.ts +++ b/packages/server-nest/src/modules/Attachments/AttachmentsApplication.ts @@ -19,7 +19,6 @@ export class AttachmentsApplication { /** * Saves the metadata of uploaded document to S3 on database. - * @param {number} tenantId * @param {} file * @returns {Promise} */ @@ -38,7 +37,6 @@ export class AttachmentsApplication { /** * Retrieves the document data. - * @param {number} tenantId * @param {string} documentKey */ public get(documentKey: string) { @@ -58,7 +56,6 @@ export class AttachmentsApplication { /** * Unlinks the given document from resource model. - * @param {number} tenantId * @param {string} filekey * @param {string} modelRef * @param {number} modelId @@ -70,7 +67,6 @@ export class AttachmentsApplication { /** * Retrieves the presigned url of the given attachment key. - * @param {number} tenantId * @param {string} key * @returns {Promise} */ diff --git a/packages/server-nest/src/modules/Attachments/DeleteAttachment.ts b/packages/server-nest/src/modules/Attachments/DeleteAttachment.ts index b2f3fc5b3..dea4adb35 100644 --- a/packages/server-nest/src/modules/Attachments/DeleteAttachment.ts +++ b/packages/server-nest/src/modules/Attachments/DeleteAttachment.ts @@ -1,8 +1,8 @@ +import { Inject, Injectable } from '@nestjs/common'; import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { Knex } from 'knex'; -import { Inject, Injectable } from '@nestjs/common'; -import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { ConfigService } from '@nestjs/config'; +import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { S3_CLIENT } from '../S3/S3.module'; import { DocumentModel } from './models/Document.model'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; @@ -17,14 +17,14 @@ export class DeleteAttachment { @Inject(S3_CLIENT) private readonly s3Client: S3Client, - @Inject(Document.name) + @Inject(DocumentModel.name) private readonly documentModel: TenantModelProxy, @Inject(DocumentLinkModel.name) - private readonly documentLinkModel: TenantModelProxy - ) { - - } + private readonly documentLinkModel: TenantModelProxy< + typeof DocumentLinkModel + >, + ) {} /** * Deletes the give file attachment file key. @@ -37,13 +37,15 @@ export class DeleteAttachment { }; await this.s3Client.send(new DeleteObjectCommand(params)); - const foundDocument = await this.documentModel().query() + const foundDocument = await this.documentModel() + .query() .findOne('key', filekey) .throwIfNotFound(); await this.uow.withTransaction(async (trx: Knex.Transaction) => { // Delete all document links - await this.documentLinkModel().query(trx) + await this.documentLinkModel() + .query(trx) .where('documentId', foundDocument.id) .delete(); diff --git a/packages/server-nest/src/modules/Attachments/GetAttachment.ts b/packages/server-nest/src/modules/Attachments/GetAttachment.ts index 2ec83fc3f..5c448200a 100644 --- a/packages/server-nest/src/modules/Attachments/GetAttachment.ts +++ b/packages/server-nest/src/modules/Attachments/GetAttachment.ts @@ -9,13 +9,11 @@ export class GetAttachment { private readonly configService: ConfigService, @Inject(S3_CLIENT) - private readonly s3: S3Client - ) { - - } + private readonly s3: S3Client, + ) {} + /** * Retrieves data of the given document key. - * @param {number} tenantId * @param {string} filekey */ async getAttachment(filekey: string) { diff --git a/packages/server-nest/src/modules/Attachments/GetAttachmentPresignedUrl.ts b/packages/server-nest/src/modules/Attachments/GetAttachmentPresignedUrl.ts index ea4a89434..3d30bbd24 100644 --- a/packages/server-nest/src/modules/Attachments/GetAttachmentPresignedUrl.ts +++ b/packages/server-nest/src/modules/Attachments/GetAttachmentPresignedUrl.ts @@ -1,35 +1,42 @@ -import { GetObjectCommand } from '@aws-sdk/client-s3'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { DocumentModel } from './models/Document.model'; +import { ConfigService } from '@nestjs/config'; +import { S3_CLIENT } from '../S3/S3.module'; @Injectable() export class getAttachmentPresignedUrl { constructor( - private readonly documentModel: TenantModelProxy + private readonly configService: ConfigService, + + @Inject(DocumentModel.name) + private readonly documentModel: TenantModelProxy, + + @Inject(S3_CLIENT) + private readonly s3Client: S3Client, ) {} /** * Retrieves the presigned url of the given attachment key with the original filename. - * @param {number} tenantId - * @param {string} key + * @param {string} key - * @returns {string} */ - async getPresignedUrl(tenantId: number, key: string) { + async getPresignedUrl(key: string) { const foundDocument = await this.documentModel().query().findOne({ key }); + const config = this.configService.get('s3'); let ResponseContentDisposition = 'attachment'; if (foundDocument && foundDocument.originName) { ResponseContentDisposition += `; filename="${foundDocument.originName}"`; } - const command = new GetObjectCommand({ - Bucket: config.s3.bucket, + Bucket: config.bucket, Key: key, ResponseContentDisposition, }); - const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); + const signedUrl = await getSignedUrl(this.s3Client, command, { expiresIn: 300 }); return signedUrl; } diff --git a/packages/server-nest/src/modules/Attachments/LinkAttachment.ts b/packages/server-nest/src/modules/Attachments/LinkAttachment.ts index 92e6f3362..4bffeadd5 100644 --- a/packages/server-nest/src/modules/Attachments/LinkAttachment.ts +++ b/packages/server-nest/src/modules/Attachments/LinkAttachment.ts @@ -1,46 +1,59 @@ -import { Inject, Service } from 'typedi'; +import { ModuleRef } from '@nestjs/core'; import bluebird from 'bluebird'; import { Knex } from 'knex'; import { validateLinkModelEntryExists, validateLinkModelExists, -} from './Attachments/_utils'; -import HasTenancyService from '../Tenancy/TenancyService'; -import { ServiceError } from '@/exceptions'; +} from './_utils'; import { ERRORS } from './constants'; +import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { DocumentLink } from '../ChromiumlyTenancy/models/DocumentLink'; +import { Inject, Injectable } from '@nestjs/common'; +import { ServiceError } from '../Items/ServiceError'; +import { getAttachableModelsMap } from './decorators/InjectAttachable.decorator'; +import { DocumentModel } from './models/Document.model'; +import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice'; -@Service() +@Injectable() export class LinkAttachment { - @Inject() - private tenancy: HasTenancyService; + constructor( + private moduleRef: ModuleRef, + + @Inject(DocumentLink.name) + private readonly documentLinkModel: TenantModelProxy, + + @Inject(DocumentModel.name) + private readonly documentModel: TenantModelProxy, + ) {} /** * Links the given file key to the given model type and id. - * @param {number} tenantId - * @param {string} filekey - * @param {string} modelRef - * @param {number} modelId + * @param {string} filekey - File key. + * @param {string} modelRef - Model reference. + * @param {number} modelId - Model id. * @returns {Promise} */ async link( - tenantId: number, filekey: string, modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ) { - const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId); - const LinkModel = models[modelRef]; - validateLinkModelExists(LinkModel); + const attachmentsAttachableModels = getAttachableModelsMap(); + const attachableModel = attachmentsAttachableModels.get(modelRef); - const foundFile = await Document.query(trx) + validateLinkModelExists(attachableModel); + + const LinkModel = this.moduleRef.get(modelRef, { strict: false }); + const foundFile = await this.documentModel() + .query(trx) .findOne('key', filekey) .throwIfNotFound(); - const foundLinkModel = await LinkModel.query(trx).findById(modelId); + const foundLinkModel = await LinkModel().query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); - const foundLinks = await DocumentLink.query(trx) + const foundLinks = await this.documentLinkModel().query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .where('documentId', foundFile.id); @@ -48,7 +61,7 @@ export class LinkAttachment { if (foundLinks.length > 0) { throw new ServiceError(ERRORS.DOCUMENT_LINK_ALREADY_LINKED); } - await DocumentLink.query(trx).insert({ + await this.documentLinkModel().query(trx).insert({ modelRef, modelId, documentId: foundFile.id, @@ -57,23 +70,21 @@ export class LinkAttachment { /** * Links the given file keys to the given model type and id. - * @param {number} tenantId - * @param {string[]} filekeys - * @param {string} modelRef - * @param {number} modelId - * @param {Knex.Transaction} trx + * @param {string[]} filekeys - File keys. + * @param {string} modelRef - Model reference. + * @param {number} modelId - Model id. + * @param {Knex.Transaction} trx - Knex transaction. * @returns {Promise} */ async bulkLink( - tenantId: number, filekeys: string[], modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ) { return bluebird.each(filekeys, async (fieldKey: string) => { try { - await this.link(tenantId, fieldKey, modelRef, modelId, trx); + await this.link(fieldKey, modelRef, modelId, trx); } catch { // Ignore catching exceptions in bulk action. } diff --git a/packages/server-nest/src/modules/Attachments/S3UploadPipeline.ts b/packages/server-nest/src/modules/Attachments/S3UploadPipeline.ts index 26d00f124..be93d598c 100644 --- a/packages/server-nest/src/modules/Attachments/S3UploadPipeline.ts +++ b/packages/server-nest/src/modules/Attachments/S3UploadPipeline.ts @@ -1,13 +1,13 @@ import { NextFunction, Request, Response } from 'express'; -import multer from 'multer'; -import type { Multer } from 'multer'; -import multerS3 from 'multer-s3'; -import { s3 } from '@/lib/S3/S3'; -import { Service } from 'typedi'; -import config from '@/config'; +import { ConfigService } from '@nestjs/config'; +import { Injectable } from '@nestjs/common'; -@Service() +@Injectable() export class AttachmentUploadPipeline { + constructor( + private readonly configService: ConfigService + ) {} + /** * Middleware to ensure that S3 configuration is properly set before proceeding. * This function checks if the necessary S3 configuration keys are present and throws an error if any are missing. @@ -16,44 +16,21 @@ export class AttachmentUploadPipeline { * @param next The callback to pass control to the next middleware function. */ public validateS3Configured(req: Request, res: Response, next: NextFunction) { + const config = this.configService.get('s3'); + if ( - !config.s3.region || - !config.s3.accessKeyId || - !config.s3.secretAccessKey + !config.region || + !config.accessKeyId || + !config.secretAccessKey ) { const missingKeys = []; - if (!config.s3.region) missingKeys.push('region'); - if (!config.s3.accessKeyId) missingKeys.push('accessKeyId'); - if (!config.s3.secretAccessKey) missingKeys.push('secretAccessKey'); + if (!config.region) missingKeys.push('region'); + if (!config.accessKeyId) missingKeys.push('accessKeyId'); + if (!config.secretAccessKey) missingKeys.push('secretAccessKey'); const missing = missingKeys.join(', '); throw new Error(`S3 configuration error: Missing ${missing}`); } next(); } - - /** - * Express middleware for uploading attachments to an S3 bucket. - * It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads. - */ - public uploadPipeline(): Multer { - return multer({ - storage: multerS3({ - s3, - bucket: config.s3.bucket, - contentType: multerS3.AUTO_CONTENT_TYPE, - metadata: function (req, file, cb) { - cb(null, { fieldName: file.fieldname }); - }, - key: function (req, file, cb) { - cb(null, Date.now().toString()); - }, - acl: function(req, file, cb) { - // Conditionally set file to public or private based on isPublic flag - const aclValue = true ? 'public-read' : 'private'; - cb(null, aclValue); // Set ACL based on the isPublic flag - } - }), - }); - } } diff --git a/packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts b/packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts index f77360260..a345a998c 100644 --- a/packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts +++ b/packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts @@ -1,40 +1,56 @@ import bluebird from 'bluebird'; +import { difference } from 'lodash'; import { validateLinkModelEntryExists, validateLinkModelExists, } from './_utils'; import { Knex } from 'knex'; -import { difference } from 'lodash'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; +import { DocumentLinkModel } from './models/DocumentLink.model'; +import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { DocumentModel } from './models/Document.model'; +import { getAttachableModelsMap } from './decorators/InjectAttachable.decorator'; +import { ModuleRef } from '@nestjs/core'; @Injectable() export class UnlinkAttachment { + constructor( + private moduleRef: ModuleRef, + + @Inject(DocumentModel.name) + private readonly documentModel: TenantModelProxy, + + @Inject(DocumentLinkModel.name) + private readonly documentLinkModel: TenantModelProxy< + typeof DocumentLinkModel + >, + ) {} + /** * Unlink the attachments from the model entry. - * @param {number} tenantId - * @param {string} filekey - * @param {string} modelRef - * @param {number} modelId + * @param {string} filekey - File key. + * @param {string} modelRef - Model reference. + * @param {number} modelId - Model id. */ async unlink( - tenantId: number, filekey: string, modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { - const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId); - - const LinkModel = models[modelRef]; - validateLinkModelExists(LinkModel); + const attachmentsAttachableModels = getAttachableModelsMap(); + const attachableModel = attachmentsAttachableModels.get(modelRef); + validateLinkModelExists(attachableModel); + + const LinkModel = this.moduleRef.get(modelRef, { strict: false }); const foundLinkModel = await LinkModel.query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); - const document = await Document.query(trx).findOne('key', filekey); + const document = await this.documentModel().query(trx).findOne('key', filekey); // Delete the document link. - await DocumentLink.query(trx) + await this.documentLinkModel().query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .where('documentId', document.id) @@ -43,22 +59,20 @@ export class UnlinkAttachment { /** * Bulk unlink the attachments from the model entry. - * @param {number} tenantId * @param {string} fieldkey * @param {string} modelRef * @param {number} modelId * @returns {Promise} */ async bulkUnlink( - tenantId: number, filekeys: string[], modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { await bluebird.each(filekeys, (fieldKey: string) => { try { - this.unlink(tenantId, fieldKey, modelRef, modelId, trx); + this.unlink(fieldKey, modelRef, modelId, trx); } catch { // Ignore catching exceptions on bulk action. } @@ -74,15 +88,13 @@ export class UnlinkAttachment { * @param {Knex.Transaction} trx */ async unlinkUnpresentedKeys( - tenantId: number, presentedKeys: string[], modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { - const { DocumentLink } = this.tenancy.models(tenantId); - - const modelLinks = await DocumentLink.query(trx) + const modelLinks = await this.documentLinkModel() + .query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .withGraphFetched('document'); @@ -90,33 +102,30 @@ export class UnlinkAttachment { const modelLinkKeys = modelLinks.map((link) => link.document.key); const unpresentedKeys = difference(modelLinkKeys, presentedKeys); - await this.bulkUnlink(tenantId, unpresentedKeys, modelRef, modelId, trx); + await this.bulkUnlink(unpresentedKeys, modelRef, modelId, trx); } /** * Unlink all attachments of the given model type and id. - * @param {number} tenantId * @param {string} modelRef * @param {number} modelId * @param {Knex.Transaction} trx * @returns {Promise} */ async unlinkAllModelKeys( - tenantId: number, modelRef: string, modelId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { - const { DocumentLink } = this.tenancy.models(tenantId); - // Get all the keys of the modelRef and modelId. - const modelLinks = await DocumentLink.query(trx) + const modelLinks = await this.documentLinkModel() + .query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .withGraphFetched('document'); const modelLinkKeys = modelLinks.map((link) => link.document.key); - await this.bulkUnlink(tenantId, modelLinkKeys, modelRef, modelId, trx); + await this.bulkUnlink(modelLinkKeys, modelRef, modelId, trx); } } diff --git a/packages/server-nest/src/modules/Attachments/ValidateAttachments.ts b/packages/server-nest/src/modules/Attachments/ValidateAttachments.ts index d629ac13b..27dc7ef54 100644 --- a/packages/server-nest/src/modules/Attachments/ValidateAttachments.ts +++ b/packages/server-nest/src/modules/Attachments/ValidateAttachments.ts @@ -1,24 +1,21 @@ import { castArray, difference } from 'lodash'; -import HasTenancyService from '../Tenancy/TenancyService'; -import { ServiceError } from '@/exceptions'; -import { Inject, Service } from 'typedi'; +import { Inject, Injectable } from '@nestjs/common'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { DocumentModel } from './models/Document.model'; +import { ServiceError } from '../Items/ServiceError'; -@Service() +@Injectable() export class ValidateAttachments { - constructor( - private readonly documentModel: TenantModelProxy - ) { + @Inject(DocumentModel.name) + private readonly documentModel: TenantModelProxy, + ) {} - } /** * Validates the given file keys existance. - * @param {number} tenantId * @param {string|string[]} key */ - async validate(tenantId: number, key: string | string[]) { + async validate(key: string | string[]) { const keys = castArray(key); const documents = await this.documentModel().query().whereIn('key', key); const documentKeys = documents.map((document) => document.key); diff --git a/packages/server-nest/src/modules/Attachments/_utils.ts b/packages/server-nest/src/modules/Attachments/_utils.ts index 26f0f59c9..fd67ab6e0 100644 --- a/packages/server-nest/src/modules/Attachments/_utils.ts +++ b/packages/server-nest/src/modules/Attachments/_utils.ts @@ -1,4 +1,4 @@ -import { ServiceError } from '@/exceptions'; +import { ServiceError } from '../Items/ServiceError'; import { ERRORS } from './constants'; export const validateLinkModelExists = (LinkModel) => { diff --git a/packages/server-nest/src/modules/Attachments/decorators/InjectAttachable.decorator.ts b/packages/server-nest/src/modules/Attachments/decorators/InjectAttachable.decorator.ts new file mode 100644 index 000000000..f62fde8d0 --- /dev/null +++ b/packages/server-nest/src/modules/Attachments/decorators/InjectAttachable.decorator.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; + +// Array to store attachable model names +const attachableModelsMap: Map = new Map(); + +/** + * Decorator that marks a class as attachable and adds its name to the attachable models array. + * This is used to track which models can have attachments. + */ +export function InjectAttachable() { + return function (target: any) { + // Add the model name to the attachable models array + attachableModelsMap.set(target.name, true); + }; +} + +/** + * Get all attachable model names + */ +export function getAttachableModelsMap() { + return attachableModelsMap; +} + diff --git a/packages/server-nest/src/modules/Attachments/dtos/Attachment.dto.ts b/packages/server-nest/src/modules/Attachments/dtos/Attachment.dto.ts index ace202de4..6a70f3245 100644 --- a/packages/server-nest/src/modules/Attachments/dtos/Attachment.dto.ts +++ b/packages/server-nest/src/modules/Attachments/dtos/Attachment.dto.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from "@nestjs/swagger"; import { IsNotEmpty, IsString } from "class-validator"; @@ -6,3 +7,27 @@ export class AttachmentLinkDto { @IsNotEmpty() key: string; } + + +export class UnlinkAttachmentDto { + @IsNotEmpty() + modelRef: string; + + + @IsNotEmpty() + modelId: number; +} + +export class LinkAttachmentDto { + @IsNotEmpty() + modelRef: string; + + + @IsNotEmpty() + modelId: number; +} + +export class UploadAttachmentDto { + @ApiProperty({ type: 'string', format: 'binary' }) + file: any; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnBills.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnBills.ts index 6909626ec..9e9dc4b14 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnBills.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnBills.ts @@ -1,68 +1,45 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { IBIllEventDeletedPayload, IBillCreatedPayload, IBillCreatingPayload, IBillEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/Bills/Bills.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnBills { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - /** - * Constructor method. + * @param {LinkAttachment} linkAttachmentService + * @param {UnlinkAttachment} unlinkAttachmentService + * @param {ValidateAttachments} validateDocuments */ - public attach(bus) { - bus.subscribe( - events.bill.onCreating, - this.validateAttachmentsOnBillCreate.bind(this) - ); - bus.subscribe( - events.bill.onCreated, - this.handleAttachmentsOnBillCreated.bind(this) - ); - bus.subscribe( - events.bill.onEdited, - this.handleUnlinkUnpresentedKeysOnBillEdited.bind(this) - ); - bus.subscribe( - events.bill.onEdited, - this.handleLinkPresentedKeysOnBillEdited.bind(this) - ); - bus.subscribe( - events.bill.onDeleting, - this.handleUnlinkAttachmentsOnBillDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating bill. * @param {ISaleInvoiceCreatingPaylaod} * @returns {Promise} */ - private async validateAttachmentsOnBillCreate({ + @OnEvent(events.bill.onCreating) + async validateAttachmentsOnBillCreate({ billDTO, - tenantId, }: IBillCreatingPayload): Promise { if (isEmpty(billDTO.attachments)) { return; } const documentKeys = billDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +47,8 @@ export class AttachmentsOnBills { * @param {ISaleInvoiceCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnBillCreated({ - tenantId, + @OnEvent(events.bill.onCreated) + async handleAttachmentsOnBillCreated({ bill, billDTO, trx, @@ -79,32 +56,27 @@ export class AttachmentsOnBills { if (isEmpty(billDTO.attachments)) return; const keys = billDTO.attachments?.map((attachment) => attachment.key); - await this.linkAttachmentService.bulkLink( - tenantId, - keys, - 'Bill', - bill.id, - trx - ); + + await this.linkAttachmentService.bulkLink(keys, 'Bill', bill.id, trx); } /** * Handles unlinking all the unpresented keys of the edited bill. * @param {IBillEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnBillEdited({ - tenantId, + @OnEvent(events.bill.onEdited) + async handleUnlinkUnpresentedKeysOnBillEdited({ billDTO, bill, - trx + trx, }: IBillEditedPayload) { const keys = billDTO.attachments?.map((attachment) => attachment.key); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'Bill', bill.id, - trx + trx, ); } @@ -113,8 +85,8 @@ export class AttachmentsOnBills { * @param {ISaleInvoiceEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnBillEdited({ - tenantId, + @OnEvent(events.bill.onEdited) + async handleLinkPresentedKeysOnBillEdited({ billDTO, oldBill, trx, @@ -122,13 +94,8 @@ export class AttachmentsOnBills { if (isEmpty(billDTO.attachments)) return; const keys = billDTO.attachments?.map((attachment) => attachment.key); - await this.linkAttachmentService.bulkLink( - tenantId, - keys, - 'Bill', - oldBill.id, - trx - ); + + await this.linkAttachmentService.bulkLink(keys, 'Bill', oldBill.id, trx); } /** @@ -136,16 +103,15 @@ export class AttachmentsOnBills { * @param {ISaleInvoiceDeletedPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnBillDeleted({ - tenantId, + @OnEvent(events.bill.onDeleting) + async handleUnlinkAttachmentsOnBillDeleted({ oldBill, trx, }: IBIllEventDeletedPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'Bill', oldBill.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnCreditNote.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnCreditNote.ts index 32547b036..9fecf3f2f 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnCreditNote.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnCreditNote.ts @@ -1,68 +1,45 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { ICreditNoteCreatedPayload, ICreditNoteCreatingPayload, ICreditNoteDeletingPayload, ICreditNoteEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/CreditNotes/types/CreditNotes.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { Injectable } from '@nestjs/common'; +import { LinkAttachment } from '../LinkAttachment'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnCreditNote { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - /** - * Constructor method. + * @param {LinkAttachment} linkAttachmentService - + * @param {UnlinkAttachment} unlinkAttachmentService - + * @param {ValidateAttachments} validateDocuments - */ - public attach(bus) { - bus.subscribe( - events.creditNote.onCreating, - this.validateAttachmentsOnCreditNoteCreate.bind(this) - ); - bus.subscribe( - events.creditNote.onCreated, - this.handleAttachmentsOnCreditNoteCreated.bind(this) - ); - bus.subscribe( - events.creditNote.onEdited, - this.handleUnlinkUnpresentedKeysOnCreditNoteEdited.bind(this) - ); - bus.subscribe( - events.creditNote.onEdited, - this.handleLinkPresentedKeysOnCreditNoteEdited.bind(this) - ); - bus.subscribe( - events.creditNote.onDeleting, - this.handleUnlinkAttachmentsOnCreditNoteDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating credit note. * @param {ICreditNoteCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnCreditNoteCreate({ + @OnEvent(events.creditNote.onCreating) + async validateAttachmentsOnCreditNoteCreate({ creditNoteDTO, - tenantId, }: ICreditNoteCreatingPayload): Promise { if (isEmpty(creditNoteDTO.attachments)) { return; } const documentKeys = creditNoteDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +47,8 @@ export class AttachmentsOnCreditNote { * @param {ICreditNoteCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnCreditNoteCreated({ - tenantId, + @OnEvent(events.creditNote.onCreated) + async handleAttachmentsOnCreditNoteCreated({ creditNote, creditNoteDTO, trx, @@ -79,12 +56,12 @@ export class AttachmentsOnCreditNote { if (isEmpty(creditNoteDTO.attachments)) return; const keys = creditNoteDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( - tenantId, keys, 'CreditNote', creditNote.id, - trx + trx, ); } @@ -92,21 +69,20 @@ export class AttachmentsOnCreditNote { * Handles unlinking all the unpresented keys of the edited credit note. * @param {ICreditNoteEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnCreditNoteEdited({ - tenantId, + @OnEvent(events.creditNote.onEdited) + async handleUnlinkUnpresentedKeysOnCreditNoteEdited({ creditNoteEditDTO, oldCreditNote, trx, }: ICreditNoteEditedPayload) { const keys = creditNoteEditDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'CreditNote', oldCreditNote.id, - trx + trx, ); } @@ -115,8 +91,8 @@ export class AttachmentsOnCreditNote { * @param {ICreditNoteEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnCreditNoteEdited({ - tenantId, + @OnEvent(events.creditNote.onEdited) + async handleLinkPresentedKeysOnCreditNoteEdited({ creditNoteEditDTO, oldCreditNote, trx, @@ -124,14 +100,13 @@ export class AttachmentsOnCreditNote { if (isEmpty(creditNoteEditDTO.attachments)) return; const keys = creditNoteEditDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'CreditNote', oldCreditNote.id, - trx + trx, ); } @@ -140,16 +115,15 @@ export class AttachmentsOnCreditNote { * @param {ICreditNoteDeletingPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnCreditNoteDeleted({ - tenantId, + @OnEvent(events.creditNote.onDeleting) + async handleUnlinkAttachmentsOnCreditNoteDeleted({ oldCreditNote, trx, }: ICreditNoteDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'CreditNote', oldCreditNote.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnExpenses.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnExpenses.ts index 77babe6c0..d501001a1 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnExpenses.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnExpenses.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; import { IExpenseCreatedPayload, IExpenseCreatingPayload, IExpenseDeletingPayload, IExpenseEventEditPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/Expenses/Expenses.types'; import { ValidateAttachments } from '../ValidateAttachments'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnExpenses { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.expenses.onCreating, - this.validateAttachmentsOnExpenseCreate.bind(this) - ); - bus.subscribe( - events.expenses.onCreated, - this.handleAttachmentsOnExpenseCreated.bind(this) - ); - bus.subscribe( - events.expenses.onEdited, - this.handleUnlinkUnpresentedKeysOnExpenseEdited.bind(this) - ); - bus.subscribe( - events.expenses.onEdited, - this.handleLinkPresentedKeysOnExpenseEdited.bind(this) - ); - bus.subscribe( - events.expenses.onDeleting, - this.handleUnlinkAttachmentsOnExpenseDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating expense. * @param {ISaleInvoiceCreatingPaylaod} * @returns {Promise} */ - private async validateAttachmentsOnExpenseCreate({ + @OnEvent(events.expenses.onCreating) + async validateAttachmentsOnExpenseCreate({ expenseDTO, - tenantId, }: IExpenseCreatingPayload): Promise { if (isEmpty(expenseDTO.attachments)) { return; } const documentKeys = expenseDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnExpenses { * @param {ISaleInvoiceCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnExpenseCreated({ - tenantId, + @OnEvent(events.expenses.onCreated) + async handleAttachmentsOnExpenseCreated({ expenseDTO, expense, trx, @@ -79,12 +51,12 @@ export class AttachmentsOnExpenses { if (isEmpty(expenseDTO.attachments)) return; const keys = expenseDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( - tenantId, keys, 'Expense', expense.id, - trx + trx, ); } @@ -92,19 +64,18 @@ export class AttachmentsOnExpenses { * Handles unlinking all the unpresented keys of the edited expense. * @param {ISaleInvoiceEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnExpenseEdited({ - tenantId, + @OnEvent(events.expenses.onEdited) + async handleUnlinkUnpresentedKeysOnExpenseEdited({ expenseDTO, expense, trx, }: IExpenseEventEditPayload) { const keys = expenseDTO.attachments?.map((attachment) => attachment.key); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'Expense', expense.id, - trx + trx, ); } @@ -113,8 +84,8 @@ export class AttachmentsOnExpenses { * @param {ISaleInvoiceEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnExpenseEdited({ - tenantId, + @OnEvent(events.expenses.onEdited) + async handleLinkPresentedKeysOnExpenseEdited({ expenseDTO, oldExpense, trx, @@ -123,11 +94,10 @@ export class AttachmentsOnExpenses { const keys = expenseDTO.attachments?.map((attachment) => attachment.key); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'Expense', oldExpense.id, - trx + trx, ); } @@ -136,16 +106,15 @@ export class AttachmentsOnExpenses { * @param {ISaleInvoiceDeletedPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnExpenseDeleted({ - tenantId, + @OnEvent(events.expenses.onDeleting) + async handleUnlinkAttachmentsOnExpenseDeleted({ oldExpense, trx, }: IExpenseDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'Expense', oldExpense.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnManualJournals.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnManualJournals.ts index b0a133d3a..076047c0a 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnManualJournals.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnManualJournals.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { IManualJournalCreatingPayload, IManualJournalEventCreatedPayload, IManualJournalEventDeletedPayload, IManualJournalEventEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/ManualJournals/types/ManualJournals.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { OnEvent } from '@nestjs/event-emitter'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { Injectable } from '@nestjs/common'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnManualJournals { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.manualJournals.onCreating, - this.validateAttachmentsOnManualJournalCreate.bind(this) - ); - bus.subscribe( - events.manualJournals.onCreated, - this.handleAttachmentsOnManualJournalCreated.bind(this) - ); - bus.subscribe( - events.manualJournals.onEdited, - this.handleUnlinkUnpresentedKeysOnManualJournalEdited.bind(this) - ); - bus.subscribe( - events.manualJournals.onEdited, - this.handleLinkPresentedKeysOnManualJournalEdited.bind(this) - ); - bus.subscribe( - events.manualJournals.onDeleting, - this.handleUnlinkAttachmentsOnManualJournalDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating manual journal. * @param {IManualJournalCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnManualJournalCreate({ + @OnEvent(events.manualJournals.onCreating) + async validateAttachmentsOnManualJournalCreate({ manualJournalDTO, - tenantId, }: IManualJournalCreatingPayload): Promise { if (isEmpty(manualJournalDTO.attachments)) { return; } const documentKeys = manualJournalDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnManualJournals { * @param {IManualJournalEventCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnManualJournalCreated({ - tenantId, + @OnEvent(events.manualJournals.onCreated) + async handleAttachmentsOnManualJournalCreated({ manualJournalDTO, manualJournal, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnManualJournals { if (isEmpty(manualJournalDTO.attachments)) return; const keys = manualJournalDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'ManualJournal', manualJournal.id, - trx + trx, ); } @@ -94,21 +65,20 @@ export class AttachmentsOnManualJournals { * Handles unlinking all the unpresented keys of the edited manual journal. * @param {ISaleInvoiceEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnManualJournalEdited({ - tenantId, + @OnEvent(events.manualJournals.onEdited) + async handleUnlinkUnpresentedKeysOnManualJournalEdited({ manualJournalDTO, manualJournal, - trx + trx, }: IManualJournalEventEditedPayload) { const keys = manualJournalDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'SaleInvoice', manualJournal.id, - trx + trx, ); } @@ -117,8 +87,8 @@ export class AttachmentsOnManualJournals { * @param {ISaleInvoiceEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnManualJournalEdited({ - tenantId, + @OnEvent(events.manualJournals.onEdited) + async handleLinkPresentedKeysOnManualJournalEdited({ manualJournalDTO, oldManualJournal, trx, @@ -126,14 +96,13 @@ export class AttachmentsOnManualJournals { if (isEmpty(manualJournalDTO.attachments)) return; const keys = manualJournalDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'ManualJournal', oldManualJournal.id, - trx + trx, ); } @@ -142,16 +111,15 @@ export class AttachmentsOnManualJournals { * @param {ISaleInvoiceDeletedPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnManualJournalDeleted({ - tenantId, + @OnEvent(events.manualJournals.onDeleting) + async handleUnlinkAttachmentsOnManualJournalDeleted({ oldManualJournal, trx, }: IManualJournalEventDeletedPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'SaleInvoice', oldManualJournal.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsMade.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsMade.ts index 276e1abbb..daa26f45b 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsMade.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsMade.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { IBillPaymentCreatingPayload, IBillPaymentDeletingPayload, IBillPaymentEventCreatedPayload, IBillPaymentEventEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/BillPayments/types/BillPayments.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { Injectable } from '@nestjs/common'; +import { LinkAttachment } from '../LinkAttachment'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { events } from '@/common/events/events'; +import { OnEvent } from '@nestjs/event-emitter'; -@Service() +@Injectable() export class AttachmentsOnBillPayments { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.billPayment.onCreating, - this.validateAttachmentsOnBillPaymentCreate.bind(this) - ); - bus.subscribe( - events.billPayment.onCreated, - this.handleAttachmentsOnBillPaymentCreated.bind(this) - ); - bus.subscribe( - events.billPayment.onEdited, - this.handleUnlinkUnpresentedKeysOnBillPaymentEdited.bind(this) - ); - bus.subscribe( - events.billPayment.onEdited, - this.handleLinkPresentedKeysOnBillPaymentEdited.bind(this) - ); - bus.subscribe( - events.billPayment.onDeleting, - this.handleUnlinkAttachmentsOnBillPaymentDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating bill payment. * @param {IBillPaymentCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnBillPaymentCreate({ + @OnEvent(events.billPayment.onCreating) + async validateAttachmentsOnBillPaymentCreate({ billPaymentDTO, - tenantId, }: IBillPaymentCreatingPayload): Promise { if (isEmpty(billPaymentDTO.attachments)) { return; } const documentKeys = billPaymentDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnBillPayments { * @param {IBillPaymentEventCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnBillPaymentCreated({ - tenantId, + @OnEvent(events.billPayment.onCreated) + async handleAttachmentsOnBillPaymentCreated({ billPaymentDTO, billPayment, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnBillPayments { if (isEmpty(billPaymentDTO.attachments)) return; const keys = billPaymentDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'BillPayment', billPayment.id, - trx + trx, ); } @@ -94,21 +65,20 @@ export class AttachmentsOnBillPayments { * Handles unlinking all the unpresented keys of the edited bill payment. * @param {IBillPaymentEventEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnBillPaymentEdited({ - tenantId, + @OnEvent(events.billPayment.onEdited) + async handleUnlinkUnpresentedKeysOnBillPaymentEdited({ billPaymentDTO, oldBillPayment, trx, }: IBillPaymentEventEditedPayload) { const keys = billPaymentDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'BillPayment', oldBillPayment.id, - trx + trx, ); } @@ -117,8 +87,8 @@ export class AttachmentsOnBillPayments { * @param {IBillPaymentEventEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnBillPaymentEdited({ - tenantId, + @OnEvent(events.billPayment.onEdited) + async handleLinkPresentedKeysOnBillPaymentEdited({ billPaymentDTO, oldBillPayment, trx, @@ -126,14 +96,13 @@ export class AttachmentsOnBillPayments { if (isEmpty(billPaymentDTO.attachments)) return; const keys = billPaymentDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'BillPayment', oldBillPayment.id, - trx + trx, ); } @@ -142,16 +111,15 @@ export class AttachmentsOnBillPayments { * @param {IBillPaymentDeletingPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnBillPaymentDeleted({ - tenantId, + @OnEvent(events.billPayment.onDeleting) + async handleUnlinkAttachmentsOnBillPaymentDeleted({ oldBillPayment, trx, }: IBillPaymentDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'BillPayment', oldBillPayment.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsReceived.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsReceived.ts index 311fbb8ea..b73f692e9 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsReceived.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnPaymentsReceived.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { IPaymentReceivedCreatedPayload, IPaymentReceivedCreatingPayload, IPaymentReceivedDeletingPayload, IPaymentReceivedEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/PaymentReceived/types/PaymentReceived.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnPaymentsReceived { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.paymentReceive.onCreating, - this.validateAttachmentsOnPaymentCreate.bind(this) - ); - bus.subscribe( - events.paymentReceive.onCreated, - this.handleAttachmentsOnPaymentCreated.bind(this) - ); - bus.subscribe( - events.paymentReceive.onEdited, - this.handleUnlinkUnpresentedKeysOnPaymentEdited.bind(this) - ); - bus.subscribe( - events.paymentReceive.onEdited, - this.handleLinkPresentedKeysOnPaymentEdited.bind(this) - ); - bus.subscribe( - events.paymentReceive.onDeleting, - this.handleUnlinkAttachmentsOnPaymentDelete.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating payment. * @param {IPaymentReceivedCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnPaymentCreate({ + @OnEvent(events.paymentReceive.onCreating) + async validateAttachmentsOnPaymentCreate({ paymentReceiveDTO, - tenantId, }: IPaymentReceivedCreatingPayload): Promise { if (isEmpty(paymentReceiveDTO.attachments)) { return; } const documentKeys = paymentReceiveDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnPaymentsReceived { * @param {IPaymentReceivedCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnPaymentCreated({ - tenantId, + @OnEvent(events.paymentReceive.onCreated) + async handleAttachmentsOnPaymentCreated({ paymentReceiveDTO, paymentReceive, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnPaymentsReceived { if (isEmpty(paymentReceiveDTO.attachments)) return; const keys = paymentReceiveDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'PaymentReceive', paymentReceive.id, - trx + trx, ); } @@ -94,21 +65,20 @@ export class AttachmentsOnPaymentsReceived { * Handles unlinking all the unpresented keys of the edited payment. * @param {IPaymentReceivedEditedPayload} */ + @OnEvent(events.paymentReceive.onEdited) private async handleUnlinkUnpresentedKeysOnPaymentEdited({ - tenantId, paymentReceiveDTO, oldPaymentReceive, trx, }: IPaymentReceivedEditedPayload) { const keys = paymentReceiveDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'PaymentReceive', oldPaymentReceive.id, - trx + trx, ); } @@ -117,8 +87,8 @@ export class AttachmentsOnPaymentsReceived { * @param {IPaymentReceivedEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnPaymentEdited({ - tenantId, + @OnEvent(events.paymentReceive.onEdited) + async handleLinkPresentedKeysOnPaymentEdited({ paymentReceiveDTO, oldPaymentReceive, trx, @@ -126,14 +96,13 @@ export class AttachmentsOnPaymentsReceived { if (isEmpty(paymentReceiveDTO.attachments)) return; const keys = paymentReceiveDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'PaymentReceive', oldPaymentReceive.id, - trx + trx, ); } @@ -142,16 +111,15 @@ export class AttachmentsOnPaymentsReceived { * @param {ISaleInvoiceDeletedPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnPaymentDelete({ - tenantId, + @OnEvent(events.paymentReceive.onDeleting) + async handleUnlinkAttachmentsOnPaymentDelete({ oldPaymentReceive, trx, }: IPaymentReceivedDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'PaymentReceive', oldPaymentReceive.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleEstimates.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleEstimates.ts index 53eb483bb..a82b9925b 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleEstimates.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleEstimates.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { ISaleEstimateCreatedPayload, ISaleEstimateCreatingPayload, ISaleEstimateDeletingPayload, ISaleEstimateEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/SaleEstimates/types/SaleEstimates.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { Injectable } from '@nestjs/common'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnSaleEstimates { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.saleEstimate.onCreating, - this.validateAttachmentsOnSaleEstimateCreated.bind(this) - ); - bus.subscribe( - events.saleEstimate.onCreated, - this.handleAttachmentsOnSaleEstimateCreated.bind(this) - ); - bus.subscribe( - events.saleEstimate.onEdited, - this.handleUnlinkUnpresentedKeysOnSaleEstimateEdited.bind(this) - ); - bus.subscribe( - events.saleEstimate.onEdited, - this.handleLinkPresentedKeysOnSaleEstimateEdited.bind(this) - ); - bus.subscribe( - events.saleEstimate.onDeleting, - this.handleUnlinkAttachmentsOnSaleEstimateDelete.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating sale estimate. * @param {ISaleEstimateCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnSaleEstimateCreated({ + @OnEvent(events.saleEstimate.onCreating) + async validateAttachmentsOnSaleEstimateCreated({ estimateDTO, - tenantId, }: ISaleEstimateCreatingPayload): Promise { if (isEmpty(estimateDTO.attachments)) { return; } const documentKeys = estimateDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnSaleEstimates { * @param {ISaleEstimateCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnSaleEstimateCreated({ - tenantId, + @OnEvent(events.saleEstimate.onCreated) + async handleAttachmentsOnSaleEstimateCreated({ saleEstimateDTO, saleEstimate, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnSaleEstimates { if (isEmpty(saleEstimateDTO.attachments)) return; const keys = saleEstimateDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleEstimate', saleEstimate.id, - trx + trx, ); } @@ -94,20 +65,19 @@ export class AttachmentsOnSaleEstimates { * Handles unlinking all the unpresented keys of the edited sale estimate. * @param {ISaleEstimateEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnSaleEstimateEdited({ - tenantId, + @OnEvent(events.saleEstimate.onEdited) + async handleUnlinkUnpresentedKeysOnSaleEstimateEdited({ estimateDTO, oldSaleEstimate, - trx + trx, }: ISaleEstimateEditedPayload) { const keys = estimateDTO.attachments?.map((attachment) => attachment.key); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'SaleEstimate', oldSaleEstimate.id, - trx + trx, ); } @@ -116,8 +86,8 @@ export class AttachmentsOnSaleEstimates { * @param {ISaleEstimateEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnSaleEstimateEdited({ - tenantId, + @OnEvent(events.saleEstimate.onEdited) + async handleLinkPresentedKeysOnSaleEstimateEdited({ estimateDTO, oldSaleEstimate, trx, @@ -125,12 +95,12 @@ export class AttachmentsOnSaleEstimates { if (isEmpty(estimateDTO.attachments)) return; const keys = estimateDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleEstimate', oldSaleEstimate.id, - trx + trx, ); } @@ -139,16 +109,15 @@ export class AttachmentsOnSaleEstimates { * @param {ISaleEstimateDeletingPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnSaleEstimateDelete({ - tenantId, + @OnEvent(events.saleEstimate.onDeleting) + async handleUnlinkAttachmentsOnSaleEstimateDelete({ oldSaleEstimate, trx, }: ISaleEstimateDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'SaleEstimate', oldSaleEstimate.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleInvoice.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleInvoice.ts index 717d956dc..256e5f201 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleInvoice.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleInvoice.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; +import { Injectable } from '@nestjs/common'; import { isEmpty } from 'lodash'; +import { OnEvent } from '@nestjs/event-emitter'; import { ISaleInvoiceCreatedPayload, ISaleInvoiceCreatingPaylaod, ISaleInvoiceDeletingPayload, ISaleInvoiceEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '@/modules/SaleInvoices/SaleInvoice.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { LinkAttachment } from '../LinkAttachment'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnSaleInvoiceCreated { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.saleInvoice.onCreating, - this.validateAttachmentsOnSaleInvoiceCreate.bind(this) - ); - bus.subscribe( - events.saleInvoice.onCreated, - this.handleAttachmentsOnSaleInvoiceCreated.bind(this) - ); - bus.subscribe( - events.saleInvoice.onEdited, - this.handleUnlinkUnpresentedKeysOnInvoiceEdited.bind(this) - ); - bus.subscribe( - events.saleInvoice.onEdited, - this.handleLinkPresentedKeysOnInvoiceEdited.bind(this) - ); - bus.subscribe( - events.saleInvoice.onDeleting, - this.handleUnlinkAttachmentsOnInvoiceDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating sale invoice. * @param {ISaleInvoiceCreatingPaylaod} * @returns {Promise} */ - private async validateAttachmentsOnSaleInvoiceCreate({ + @OnEvent(events.saleInvoice.onCreating) + async validateAttachmentsOnSaleInvoiceCreate({ saleInvoiceDTO, - tenantId, }: ISaleInvoiceCreatingPaylaod): Promise { if (isEmpty(saleInvoiceDTO.attachments)) { return; } const documentKeys = saleInvoiceDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnSaleInvoiceCreated { * @param {ISaleInvoiceCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnSaleInvoiceCreated({ - tenantId, + @OnEvent(events.saleInvoice.onCreated) + async handleAttachmentsOnSaleInvoiceCreated({ saleInvoiceDTO, saleInvoice, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnSaleInvoiceCreated { if (isEmpty(saleInvoiceDTO.attachments)) return; const keys = saleInvoiceDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleInvoice', saleInvoice.id, - trx + trx, ); } @@ -94,8 +65,8 @@ export class AttachmentsOnSaleInvoiceCreated { * Handles unlinking all the unpresented keys of the edited sale invoice. * @param {ISaleInvoiceEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnInvoiceEdited({ - tenantId, + @OnEvent(events.saleInvoice.onEdited) + async handleUnlinkUnpresentedKeysOnInvoiceEdited({ saleInvoiceDTO, saleInvoice, trx, @@ -103,14 +74,13 @@ export class AttachmentsOnSaleInvoiceCreated { // if (isEmpty(saleInvoiceDTO.attachments)) return; const keys = saleInvoiceDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'SaleInvoice', saleInvoice.id, - trx + trx, ); } @@ -119,8 +89,8 @@ export class AttachmentsOnSaleInvoiceCreated { * @param {ISaleInvoiceEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnInvoiceEdited({ - tenantId, + @OnEvent(events.saleInvoice.onEdited) + async handleLinkPresentedKeysOnInvoiceEdited({ saleInvoiceDTO, oldSaleInvoice, trx, @@ -128,14 +98,13 @@ export class AttachmentsOnSaleInvoiceCreated { if (isEmpty(saleInvoiceDTO.attachments)) return; const keys = saleInvoiceDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleInvoice', oldSaleInvoice.id, - trx + trx, ); } @@ -144,16 +113,15 @@ export class AttachmentsOnSaleInvoiceCreated { * @param {ISaleInvoiceDeletedPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnInvoiceDeleted({ - tenantId, + @OnEvent(events.saleInvoice.onDeleting) + async handleUnlinkAttachmentsOnInvoiceDeleted({ oldSaleInvoice, trx, }: ISaleInvoiceDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'SaleInvoice', oldSaleInvoice.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleReceipts.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleReceipts.ts index 01a8d9fc3..cdec34b80 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleReceipts.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnSaleReceipts.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; import { ISaleReceiptCreatedPayload, ISaleReceiptCreatingPayload, ISaleReceiptDeletingPayload, ISaleReceiptEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '../../SaleReceipts/types/SaleReceipts.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { LinkAttachment } from '../LinkAttachment'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnSaleReceipt { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.saleReceipt.onCreating, - this.validateAttachmentsOnSaleInvoiceCreate.bind(this) - ); - bus.subscribe( - events.saleReceipt.onCreated, - this.handleAttachmentsOnSaleInvoiceCreated.bind(this) - ); - bus.subscribe( - events.saleReceipt.onEdited, - this.handleUnlinkUnpresentedKeysOnInvoiceEdited.bind(this) - ); - bus.subscribe( - events.saleReceipt.onEdited, - this.handleLinkPresentedKeysOnInvoiceEdited.bind(this) - ); - bus.subscribe( - events.saleReceipt.onDeleting, - this.handleUnlinkAttachmentsOnReceiptDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating sale receipt. * @param {ISaleReceiptCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnSaleInvoiceCreate({ + @OnEvent(events.saleReceipt.onCreating) + async validateAttachmentsOnSaleInvoiceCreate({ saleReceiptDTO, - tenantId, }: ISaleReceiptCreatingPayload): Promise { if (isEmpty(saleReceiptDTO.attachments)) { return; } const documentKeys = saleReceiptDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnSaleReceipt { * @param {ISaleReceiptCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnSaleInvoiceCreated({ - tenantId, + @OnEvent(events.saleReceipt.onCreated) + async handleAttachmentsOnSaleInvoiceCreated({ saleReceiptDTO, saleReceipt, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnSaleReceipt { if (isEmpty(saleReceiptDTO.attachments)) return; const keys = saleReceiptDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleReceipt', saleReceipt.id, - trx + trx, ); } @@ -94,21 +65,20 @@ export class AttachmentsOnSaleReceipt { * Handles unlinking all the unpresented keys of the edited sale receipt. * @param {ISaleReceiptEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnInvoiceEdited({ - tenantId, + @OnEvent(events.saleReceipt.onEdited) + async handleUnlinkUnpresentedKeysOnInvoiceEdited({ saleReceiptDTO, saleReceipt, trx, }: ISaleReceiptEditedPayload) { const keys = saleReceiptDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'SaleReceipt', saleReceipt.id, - trx + trx, ); } @@ -117,8 +87,8 @@ export class AttachmentsOnSaleReceipt { * @param {ISaleReceiptEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnInvoiceEdited({ - tenantId, + @OnEvent(events.saleReceipt.onEdited) + async handleLinkPresentedKeysOnInvoiceEdited({ saleReceiptDTO, oldSaleReceipt, trx, @@ -126,14 +96,13 @@ export class AttachmentsOnSaleReceipt { if (isEmpty(saleReceiptDTO.attachments)) return; const keys = saleReceiptDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'SaleReceipt', oldSaleReceipt.id, - trx + trx, ); } @@ -142,16 +111,15 @@ export class AttachmentsOnSaleReceipt { * @param {ISaleReceiptDeletingPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnReceiptDeleted({ - tenantId, + @OnEvent(events.saleReceipt.onDeleting) + async handleUnlinkAttachmentsOnReceiptDeleted({ oldSaleReceipt, trx, }: ISaleReceiptDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'SaleReceipt', oldSaleReceipt.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnVendorCredits.ts b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnVendorCredits.ts index 30ce63ca5..97543118a 100644 --- a/packages/server-nest/src/modules/Attachments/events/AttachmentsOnVendorCredits.ts +++ b/packages/server-nest/src/modules/Attachments/events/AttachmentsOnVendorCredits.ts @@ -1,68 +1,40 @@ -import { Inject, Service } from 'typedi'; import { isEmpty } from 'lodash'; import { IVendorCreditCreatedPayload, IVendorCreditCreatingPayload, IVendorCreditDeletingPayload, IVendorCreditEditedPayload, -} from '@/interfaces'; -import events from '@/subscribers/events'; -import { LinkAttachment } from '../LinkAttachment'; +} from '../../VendorCredit/types/VendorCredit.types'; import { ValidateAttachments } from '../ValidateAttachments'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; import { UnlinkAttachment } from '../UnlinkAttachment'; +import { LinkAttachment } from '../LinkAttachment'; +import { events } from '@/common/events/events'; -@Service() +@Injectable() export class AttachmentsOnVendorCredits { - @Inject() - private linkAttachmentService: LinkAttachment; - - @Inject() - private unlinkAttachmentService: UnlinkAttachment; - - @Inject() - private validateDocuments: ValidateAttachments; - - /** - * Constructor method. - */ - public attach(bus) { - bus.subscribe( - events.vendorCredit.onCreating, - this.validateAttachmentsOnVendorCreditCreate.bind(this) - ); - bus.subscribe( - events.vendorCredit.onCreated, - this.handleAttachmentsOnVendorCreditCreated.bind(this) - ); - bus.subscribe( - events.vendorCredit.onEdited, - this.handleUnlinkUnpresentedKeysOnVendorCreditEdited.bind(this) - ); - bus.subscribe( - events.vendorCredit.onEdited, - this.handleLinkPresentedKeysOnVendorCreditEdited.bind(this) - ); - bus.subscribe( - events.vendorCredit.onDeleting, - this.handleUnlinkAttachmentsOnVendorCreditDeleted.bind(this) - ); - } + constructor( + private readonly linkAttachmentService: LinkAttachment, + private readonly unlinkAttachmentService: UnlinkAttachment, + private readonly validateDocuments: ValidateAttachments, + ) {} /** * Validates the attachment keys on creating vendor credit. * @param {IVendorCreditCreatingPayload} * @returns {Promise} */ - private async validateAttachmentsOnVendorCreditCreate({ + @OnEvent(events.vendorCredit.onCreating) + async validateAttachmentsOnVendorCreditCreate({ vendorCreditCreateDTO, - tenantId, }: IVendorCreditCreatingPayload): Promise { if (isEmpty(vendorCreditCreateDTO.attachments)) { return; } const documentKeys = vendorCreditCreateDTO?.attachments?.map((a) => a.key); - await this.validateDocuments.validate(tenantId, documentKeys); + await this.validateDocuments.validate(documentKeys); } /** @@ -70,8 +42,8 @@ export class AttachmentsOnVendorCredits { * @param {IVendorCreditCreatedPayload} * @returns {Promise} */ - private async handleAttachmentsOnVendorCreditCreated({ - tenantId, + @OnEvent(events.vendorCredit.onCreated) + async handleAttachmentsOnVendorCreditCreated({ vendorCreditCreateDTO, vendorCredit, trx, @@ -79,14 +51,13 @@ export class AttachmentsOnVendorCredits { if (isEmpty(vendorCreditCreateDTO.attachments)) return; const keys = vendorCreditCreateDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'VendorCredit', vendorCredit.id, - trx + trx, ); } @@ -94,21 +65,20 @@ export class AttachmentsOnVendorCredits { * Handles unlinking all the unpresented keys of the edited vendor credit. * @param {IVendorCreditEditedPayload} */ - private async handleUnlinkUnpresentedKeysOnVendorCreditEdited({ - tenantId, + @OnEvent(events.vendorCredit.onEdited) + async handleUnlinkUnpresentedKeysOnVendorCreditEdited({ vendorCreditDTO, oldVendorCredit, trx, }: IVendorCreditEditedPayload) { const keys = vendorCreditDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.unlinkAttachmentService.unlinkUnpresentedKeys( - tenantId, keys, 'VendorCredit', oldVendorCredit.id, - trx + trx, ); } @@ -117,8 +87,8 @@ export class AttachmentsOnVendorCredits { * @param {IVendorCreditEditedPayload} * @returns {Promise} */ - private async handleLinkPresentedKeysOnVendorCreditEdited({ - tenantId, + @OnEvent(events.vendorCredit.onEdited) + async handleLinkPresentedKeysOnVendorCreditEdited({ vendorCreditDTO, oldVendorCredit, trx, @@ -126,14 +96,13 @@ export class AttachmentsOnVendorCredits { if (isEmpty(vendorCreditDTO.attachments)) return; const keys = vendorCreditDTO.attachments?.map( - (attachment) => attachment.key + (attachment) => attachment.key, ); await this.linkAttachmentService.bulkLink( - tenantId, keys, 'VendorCredit', oldVendorCredit.id, - trx + trx, ); } @@ -142,16 +111,15 @@ export class AttachmentsOnVendorCredits { * @param {IVendorCreditDeletingPayload} * @returns {Promise} */ - private async handleUnlinkAttachmentsOnVendorCreditDeleted({ - tenantId, + @OnEvent(events.vendorCredit.onDeleting) + async handleUnlinkAttachmentsOnVendorCreditDeleted({ oldVendorCredit, trx, }: IVendorCreditDeletingPayload) { await this.unlinkAttachmentService.unlinkAllModelKeys( - tenantId, 'VendorCredit', oldVendorCredit.id, - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/Attachments/models/DocumentLink.model.ts b/packages/server-nest/src/modules/Attachments/models/DocumentLink.model.ts index c218d1615..5629ca9d0 100644 --- a/packages/server-nest/src/modules/Attachments/models/DocumentLink.model.ts +++ b/packages/server-nest/src/modules/Attachments/models/DocumentLink.model.ts @@ -1,7 +1,9 @@ import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { Model, mixin } from 'objection'; +import { DocumentModel } from './Document.model'; export class DocumentLinkModel extends TenantBaseModel { + document!: DocumentModel; /** * Table name */ diff --git a/packages/server-nest/src/modules/Attachments/utils.ts b/packages/server-nest/src/modules/Attachments/utils.ts index 4f58fbc9c..1669d7d7b 100644 --- a/packages/server-nest/src/modules/Attachments/utils.ts +++ b/packages/server-nest/src/modules/Attachments/utils.ts @@ -1,9 +1,10 @@ import path from 'path'; -import config from '@/config'; +// import config from '@/config'; export const getUploadedObjectUri = (objectKey: string) => { - return new URL( - path.join(config.s3.bucket, objectKey), - config.s3.endpoint - ).toString(); + return ''; + // return new URL( + // path.join(config.s3.bucket, objectKey), + // config.s3.endpoint + // ).toString(); }; diff --git a/packages/server-nest/src/modules/ChromiumlyTenancy/models/DocumentLink.ts b/packages/server-nest/src/modules/ChromiumlyTenancy/models/DocumentLink.ts index 937ae5aa4..bbde542a5 100644 --- a/packages/server-nest/src/modules/ChromiumlyTenancy/models/DocumentLink.ts +++ b/packages/server-nest/src/modules/ChromiumlyTenancy/models/DocumentLink.ts @@ -6,7 +6,7 @@ import { BaseModel } from '@/models/Model'; export class DocumentLink extends BaseModel{ public modelRef: string; - public modelId: string; + public modelId: number; public documentId: number; public expiresAt?: Date; diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts index 0748c5cc3..967d10f79 100644 --- a/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts +++ b/packages/server-nest/src/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service.ts @@ -4,11 +4,6 @@ import * as moment from 'moment'; import * as composeAsync from 'async/compose'; import * as R from 'ramda'; import { ERRORS } from '../constants'; -import { - ICreditNoteEditDTO, - ICreditNoteEntryNewDTO, - ICreditNoteNewDTO, -} from '../types/CreditNotes.types'; import { ServiceError } from '@/modules/Items/ServiceError'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform'; diff --git a/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts index b133e868a..0de0d7293 100644 --- a/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts +++ b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts @@ -1,43 +1,9 @@ import { Knex } from 'knex'; import { CreditNote } from '../models/CreditNote'; -import { RefundCreditNote } from '../../CreditNoteRefunds/models/RefundCreditNote'; -import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; -import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; -import { EditCreditNoteDto } from '../dtos/CreditNote.dto'; - -export interface ICreditNoteEntryNewDTO extends IItemEntryDTO {} - -export interface ICreditNoteNewDTO { - customerId: number; - exchangeRate?: number; - creditNoteDate: Date; - creditNoteNumber: string; - note: string; - open: boolean; - entries: ICreditNoteEntryNewDTO[]; - branchId?: number; - warehouseId?: number; - attachments?: AttachmentLinkDTO[]; - discount?: number; - // discountType?: DiscountType; - adjustment?: number; -} - -export interface ICreditNoteEditDTO { - customerId: number; - exchangeRate?: number; - creditNoteDate: Date; - creditNoteNumber: string; - note: string; - open: boolean; - entries: ICreditNoteEntryNewDTO[]; - branchId?: number; - warehouseId?: number; - attachments?: AttachmentLinkDTO[]; -} +import { CreateCreditNoteDto, EditCreditNoteDto } from '../dtos/CreditNote.dto'; export enum CreditNoteAction { Create = 'Create', @@ -74,13 +40,13 @@ export interface ICreditNoteEditedPayload { } export interface ICreditNoteCreatedPayload { - creditNoteDTO: ICreditNoteNewDTO; + creditNoteDTO: CreateCreditNoteDto; creditNote: CreditNote; trx: Knex.Transaction; } export interface ICreditNoteCreatingPayload { - creditNoteDTO: ICreditNoteNewDTO; + creditNoteDTO: CreateCreditNoteDto; trx: Knex.Transaction; } diff --git a/packages/server-nest/src/modules/Expenses/Expenses.types.ts b/packages/server-nest/src/modules/Expenses/Expenses.types.ts index 9ccd46379..73b4de06b 100644 --- a/packages/server-nest/src/modules/Expenses/Expenses.types.ts +++ b/packages/server-nest/src/modules/Expenses/Expenses.types.ts @@ -2,6 +2,8 @@ import { Knex } from 'knex'; import { Expense } from './models/Expense.model'; import { SystemUser } from '../System/models/SystemUser'; import { IFilterRole } from '../DynamicListing/DynamicFilter/DynamicFilter.types'; +import { CreateExpenseDto, EditExpenseDto } from './dtos/Expense.dto'; +import { CreateExpense } from './commands/CreateExpense.service'; export interface IPaginationMeta { total: number; @@ -19,59 +21,28 @@ export interface IExpensesFilter { filterQuery?: (query: any) => void; } -export interface IExpenseCommonDTO { - currencyCode: string; - exchangeRate?: number; - description?: string; - paymentAccountId: number; - peyeeId?: number; - referenceNo?: string; - publish: boolean; - userId: number; - paymentDate: Date; - payeeId: number; - categories: IExpenseCategoryDTO[]; - - branchId?: number; - // attachments?: AttachmentLinkDTO[]; -} - -export interface IExpenseCreateDTO extends IExpenseCommonDTO {} -export interface IExpenseEditDTO extends IExpenseCommonDTO {} - -export interface IExpenseCategoryDTO { - id?: number; - expenseAccountId: number; - index: number; - amount: number; - description?: string; - expenseId: number; - landedCost?: boolean; - projectId?: number; -} - export interface IExpenseCreatingPayload { trx: Knex.Transaction; - expenseDTO: IExpenseCreateDTO; + expenseDTO: CreateExpenseDto; } export interface IExpenseEventEditingPayload { oldExpense: Expense; - expenseDTO: IExpenseEditDTO; + expenseDTO: EditExpenseDto; trx: Knex.Transaction; } export interface IExpenseCreatedPayload { expenseId: number; expense: Expense; - expenseDTO: IExpenseCreateDTO; + expenseDTO: CreateExpenseDto; trx?: Knex.Transaction; } export interface IExpenseEventEditPayload { expenseId: number; expense: Expense; - expenseDTO: IExpenseEditDTO; + expenseDTO: EditExpenseDto; authorizedUser: SystemUser; oldExpense: Expense; trx: Knex.Transaction; diff --git a/packages/server-nest/src/modules/S3/S3.module.ts b/packages/server-nest/src/modules/S3/S3.module.ts index f62917107..e48b9c54c 100644 --- a/packages/server-nest/src/modules/S3/S3.module.ts +++ b/packages/server-nest/src/modules/S3/S3.module.ts @@ -7,6 +7,7 @@ export const S3_CLIENT = 'S3_CLIENT'; const services = [ { provide: S3_CLIENT, + inject: [ConfigService], useFactory: (configService: ConfigService) => { const config = configService.get('s3'); diff --git a/packages/server-nest/src/modules/SaleInvoices/dtos/SaleInvoice.dto.ts b/packages/server-nest/src/modules/SaleInvoices/dtos/SaleInvoice.dto.ts index 7516e58cd..1793f2c26 100644 --- a/packages/server-nest/src/modules/SaleInvoices/dtos/SaleInvoice.dto.ts +++ b/packages/server-nest/src/modules/SaleInvoices/dtos/SaleInvoice.dto.ts @@ -29,6 +29,12 @@ class PaymentMethodDto { enable: boolean; } + +class AttachmentDto { + @IsString() + key: string; +} + class CommandSaleInvoiceDto { @IsInt() @IsNotEmpty() @@ -186,6 +192,16 @@ class CommandSaleInvoiceDto { example: 1, }) fromEstimateId?: number; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => AttachmentDto) + @ApiProperty({ + description: 'The attachments of the sale receipt', + example: [{ key: '123456' }], + }) + attachments?: AttachmentDto[]; } export class CreateSaleInvoiceDto extends CommandSaleInvoiceDto {} diff --git a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts index 15e24fc71..3d64ba94b 100644 --- a/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts +++ b/packages/server-nest/src/modules/SaleInvoices/models/SaleInvoice.ts @@ -13,7 +13,9 @@ import { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilte import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { PaymentIntegrationTransactionLink } from '../SaleInvoice.types'; import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model'; +import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator'; +@InjectAttachable() export class SaleInvoice extends TenantBaseModel{ public taxAmountWithheld: number; public balance: number; diff --git a/packages/server-nest/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts b/packages/server-nest/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts index b607fb9dd..556bb49d7 100644 --- a/packages/server-nest/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts +++ b/packages/server-nest/src/modules/SaleReceipts/commands/CreateSaleReceipt.service.ts @@ -3,7 +3,6 @@ import { Knex } from 'knex'; import { ISaleReceiptCreatedPayload, ISaleReceiptCreatingPayload, - ISaleReceiptDTO, } from '../types/SaleReceipts.types'; import { SaleReceiptDTOTransformer } from './SaleReceiptDTOTransformer.service'; import { SaleReceiptValidators } from './SaleReceiptValidators.service'; diff --git a/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts b/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts index 4eabad688..5deff7c90 100644 --- a/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts +++ b/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts @@ -1,39 +1,21 @@ import { Knex } from 'knex'; -// import { IItemEntry } from './ItemEntry'; -// import { CommonMailOptions, CommonMailOptionsDTO } from '../SaleInvoices/types/Mailable'; -import { AttachmentLinkDTO } from '../../Attachments/Attachments.types'; import { SaleReceipt } from '../models/SaleReceipt'; import { CommonMailOptionsDTO } from '@/modules/MailNotification/MailNotification.types'; import { CommonMailOptions } from '@/modules/MailNotification/MailNotification.types'; import { TenantJobPayload } from '@/interfaces/Tenant'; +import { CreateSaleReceiptDto, EditSaleReceiptDto } from '../dtos/SaleReceipt.dto'; export interface ISalesReceiptsFilter { filterQuery?: (query: any) => void; } -export interface ISaleReceiptDTO { - customerId: number; - exchangeRate?: number; - depositAccountId: number; - receiptDate: Date; - sendToEmail: string; - referenceNo?: string; - receiptNumber?: string; - receiptMessage: string; - statement: string; - closed: boolean; - entries: any[]; - branchId?: number; - attachments?: AttachmentLinkDTO[]; -} - export interface ISaleReceiptSmsDetails { customerName: string; customerPhoneNumber: string; smsMessage: string; } export interface ISaleReceiptCreatingPayload { - saleReceiptDTO: ISaleReceiptDTO; + saleReceiptDTO: CreateSaleReceiptDto; trx: Knex.Transaction; } @@ -41,21 +23,20 @@ export interface ISaleReceiptCreatedPayload { // tenantId: number; saleReceipt: SaleReceipt; saleReceiptId: number; - saleReceiptDTO: ISaleReceiptDTO; + saleReceiptDTO: CreateSaleReceiptDto; trx: Knex.Transaction; } export interface ISaleReceiptEditedPayload { oldSaleReceipt: SaleReceipt; saleReceipt: SaleReceipt; - // saleReceiptId: number; - saleReceiptDTO: ISaleReceiptDTO; + saleReceiptDTO: EditSaleReceiptDto; trx: Knex.Transaction; } export interface ISaleReceiptEditingPayload { oldSaleReceipt: SaleReceipt; - saleReceiptDTO: ISaleReceiptDTO; + saleReceiptDTO: EditSaleReceiptDto; trx: Knex.Transaction; } export interface ISaleReceiptEventClosedPayload { diff --git a/packages/server/src/services/Attachments/S3UploadPipeline.ts b/packages/server/src/services/Attachments/S3UploadPipeline.ts index 26d00f124..9e71cd39d 100644 --- a/packages/server/src/services/Attachments/S3UploadPipeline.ts +++ b/packages/server/src/services/Attachments/S3UploadPipeline.ts @@ -15,7 +15,7 @@ export class AttachmentUploadPipeline { * @param res The HTTP response object. * @param next The callback to pass control to the next middleware function. */ - public validateS3Configured(req: Request, res: Response, next: NextFunction) { + validateS3Configured(req: Request, res: Response, next: NextFunction) { if ( !config.s3.region || !config.s3.accessKeyId || @@ -42,6 +42,7 @@ export class AttachmentUploadPipeline { s3, bucket: config.s3.bucket, contentType: multerS3.AUTO_CONTENT_TYPE, + metadata: function (req, file, cb) { cb(null, { fieldName: file.fieldname }); }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be2b0bf77..20f0eb0c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -547,6 +547,9 @@ importers: '@supercharge/promise-pool': specifier: ^3.2.0 version: 3.2.0 + '@types/multer': + specifier: ^1.4.11 + version: 1.4.11 '@types/nodemailer': specifier: ^6.4.17 version: 6.4.17 @@ -631,6 +634,9 @@ importers: mathjs: specifier: ^9.4.0 version: 9.5.2 + mime-types: + specifier: ^2.1.35 + version: 2.1.35 moment: specifier: ^2.30.1 version: 2.30.1 @@ -640,6 +646,12 @@ importers: moment-timezone: specifier: ^0.5.43 version: 0.5.45 + multer: + specifier: 1.4.5-lts.1 + version: 1.4.5-lts.1 + multer-s3: + specifier: ^3.0.1 + version: 3.0.1(@aws-sdk/client-s3@3.583.0) mysql: specifier: ^2.18.1 version: 2.18.1 @@ -12460,7 +12472,6 @@ packages: resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==} dependencies: '@types/express': 4.17.21 - dev: true /@types/node-fetch@2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} @@ -12653,7 +12664,7 @@ packages: /@types/serve-index@1.9.4: resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} dependencies: - '@types/express': 4.17.21 + '@types/express': 5.0.0 dev: false /@types/serve-static@1.15.7: