refactor(nestjs): attachments module

This commit is contained in:
Ahmed Bouhuolia
2025-04-06 21:13:46 +02:00
parent 1ed77dd5ed
commit 842a862b87
41 changed files with 918 additions and 859 deletions

View File

@@ -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",

View File

@@ -0,0 +1 @@
export const MULTER_MODULE_OPTIONS = 'MULTER_MODULE_OPTIONS';

View File

@@ -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',
};

View File

@@ -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;
}

View File

@@ -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<NestInterceptor> {
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<Observable<any>> {
const ctx = context.switchToHttp();
await new Promise<void>((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;
}

View File

@@ -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 {}

View File

@@ -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<Response | void> {
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<Response | void> {
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<Response | void> {
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<Response | void> {
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<Response | void> {
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<Response | void> {
const presignedUrl =
await this.attachmentsApplication.getPresignedUrl(documentKey);
return res.status(200).send({ presignedUrl });
}
}

View File

@@ -19,7 +19,6 @@ export class AttachmentsApplication {
/**
* Saves the metadata of uploaded document to S3 on database.
* @param {number} tenantId
* @param {} file
* @returns {Promise<Document>}
*/
@@ -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<string>}
*/

View File

@@ -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<typeof DocumentModel>,
@Inject(DocumentLinkModel.name)
private readonly documentLinkModel: TenantModelProxy<typeof DocumentLinkModel>
) {
}
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();

View File

@@ -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) {

View File

@@ -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<typeof DocumentModel>
private readonly configService: ConfigService,
@Inject(DocumentModel.name)
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
@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;
}

View File

@@ -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<typeof DocumentLink>,
@Inject(DocumentModel.name)
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
) {}
/**
* 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<void>}
*/
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<void>}
*/
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.
}

View File

@@ -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
}
}),
});
}
}

View File

@@ -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<typeof DocumentModel>,
@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<void> {
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<void>}
*/
async bulkUnlink(
tenantId: number,
filekeys: string[],
modelRef: string,
modelId: number,
trx?: Knex.Transaction
trx?: Knex.Transaction,
): Promise<void> {
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<void> {
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<void>}
*/
async unlinkAllModelKeys(
tenantId: number,
modelRef: string,
modelId: number,
trx?: Knex.Transaction
trx?: Knex.Transaction,
): Promise<void> {
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);
}
}

View File

@@ -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<typeof DocumentModel>
) {
@Inject(DocumentModel.name)
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
) {}
}
/**
* 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);

View File

@@ -1,4 +1,4 @@
import { ServiceError } from '@/exceptions';
import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './constants';
export const validateLinkModelExists = (LinkModel) => {

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
// Array to store attachable model names
const attachableModelsMap: Map<string, boolean> = 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;
}

View File

@@ -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;
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnBillCreate({
@OnEvent(events.bill.onCreating)
async validateAttachmentsOnBillCreate({
billDTO,
tenantId,
}: IBillCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnBillDeleted({
tenantId,
@OnEvent(events.bill.onDeleting)
async handleUnlinkAttachmentsOnBillDeleted({
oldBill,
trx,
}: IBIllEventDeletedPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'Bill',
oldBill.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnCreditNoteCreate({
@OnEvent(events.creditNote.onCreating)
async validateAttachmentsOnCreditNoteCreate({
creditNoteDTO,
tenantId,
}: ICreditNoteCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnCreditNoteDeleted({
tenantId,
@OnEvent(events.creditNote.onDeleting)
async handleUnlinkAttachmentsOnCreditNoteDeleted({
oldCreditNote,
trx,
}: ICreditNoteDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'CreditNote',
oldCreditNote.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnExpenseCreate({
@OnEvent(events.expenses.onCreating)
async validateAttachmentsOnExpenseCreate({
expenseDTO,
tenantId,
}: IExpenseCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnExpenseDeleted({
tenantId,
@OnEvent(events.expenses.onDeleting)
async handleUnlinkAttachmentsOnExpenseDeleted({
oldExpense,
trx,
}: IExpenseDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'Expense',
oldExpense.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnManualJournalCreate({
@OnEvent(events.manualJournals.onCreating)
async validateAttachmentsOnManualJournalCreate({
manualJournalDTO,
tenantId,
}: IManualJournalCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnManualJournalDeleted({
tenantId,
@OnEvent(events.manualJournals.onDeleting)
async handleUnlinkAttachmentsOnManualJournalDeleted({
oldManualJournal,
trx,
}: IManualJournalEventDeletedPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleInvoice',
oldManualJournal.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnBillPaymentCreate({
@OnEvent(events.billPayment.onCreating)
async validateAttachmentsOnBillPaymentCreate({
billPaymentDTO,
tenantId,
}: IBillPaymentCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnBillPaymentDeleted({
tenantId,
@OnEvent(events.billPayment.onDeleting)
async handleUnlinkAttachmentsOnBillPaymentDeleted({
oldBillPayment,
trx,
}: IBillPaymentDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'BillPayment',
oldBillPayment.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnPaymentCreate({
@OnEvent(events.paymentReceive.onCreating)
async validateAttachmentsOnPaymentCreate({
paymentReceiveDTO,
tenantId,
}: IPaymentReceivedCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnPaymentDelete({
tenantId,
@OnEvent(events.paymentReceive.onDeleting)
async handleUnlinkAttachmentsOnPaymentDelete({
oldPaymentReceive,
trx,
}: IPaymentReceivedDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'PaymentReceive',
oldPaymentReceive.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleEstimateCreated({
@OnEvent(events.saleEstimate.onCreating)
async validateAttachmentsOnSaleEstimateCreated({
estimateDTO,
tenantId,
}: ISaleEstimateCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnSaleEstimateDelete({
tenantId,
@OnEvent(events.saleEstimate.onDeleting)
async handleUnlinkAttachmentsOnSaleEstimateDelete({
oldSaleEstimate,
trx,
}: ISaleEstimateDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleEstimate',
oldSaleEstimate.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleInvoiceCreate({
@OnEvent(events.saleInvoice.onCreating)
async validateAttachmentsOnSaleInvoiceCreate({
saleInvoiceDTO,
tenantId,
}: ISaleInvoiceCreatingPaylaod): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnInvoiceDeleted({
tenantId,
@OnEvent(events.saleInvoice.onDeleting)
async handleUnlinkAttachmentsOnInvoiceDeleted({
oldSaleInvoice,
trx,
}: ISaleInvoiceDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleInvoice',
oldSaleInvoice.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleInvoiceCreate({
@OnEvent(events.saleReceipt.onCreating)
async validateAttachmentsOnSaleInvoiceCreate({
saleReceiptDTO,
tenantId,
}: ISaleReceiptCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnReceiptDeleted({
tenantId,
@OnEvent(events.saleReceipt.onDeleting)
async handleUnlinkAttachmentsOnReceiptDeleted({
oldSaleReceipt,
trx,
}: ISaleReceiptDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleReceipt',
oldSaleReceipt.id,
trx
trx,
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnVendorCreditCreate({
@OnEvent(events.vendorCredit.onCreating)
async validateAttachmentsOnVendorCreditCreate({
vendorCreditCreateDTO,
tenantId,
}: IVendorCreditCreatingPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnVendorCreditDeleted({
tenantId,
@OnEvent(events.vendorCredit.onDeleting)
async handleUnlinkAttachmentsOnVendorCreditDeleted({
oldVendorCredit,
trx,
}: IVendorCreditDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'VendorCredit',
oldVendorCredit.id,
trx
trx,
);
}
}

View File

@@ -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
*/

View File

@@ -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();
};

View File

@@ -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;

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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');

View File

@@ -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 {}

View File

@@ -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;

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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 });
},

15
pnpm-lock.yaml generated
View File

@@ -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: