mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
refactor(nestjs): attachments and s3 modules
This commit is contained in:
@@ -77,6 +77,8 @@ import { PaymentServicesModule } from '../PaymentServices/PaymentServices.module
|
||||
import { AuthModule } from '../Auth/Auth.module';
|
||||
import { TenancyModule } from '../Tenancy/Tenancy.module';
|
||||
import { LoopsModule } from '../Loops/Loops.module';
|
||||
import { AttachmentsModule } from '../Attachments/Attachment.module';
|
||||
import { S3Module } from '../S3/S3.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -188,7 +190,9 @@ import { LoopsModule } from '../Loops/Loops.module';
|
||||
OrganizationModule,
|
||||
TenantDBManagerModule,
|
||||
PaymentServicesModule,
|
||||
LoopsModule
|
||||
LoopsModule,
|
||||
AttachmentsModule,
|
||||
S3Module
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { S3Module } from "../S3/S3.module";
|
||||
import { DeleteAttachment } from "./DeleteAttachment";
|
||||
import { GetAttachment } from "./GetAttachment";
|
||||
import { getAttachmentPresignedUrl } from "./GetAttachmentPresignedUrl";
|
||||
import { LinkAttachment } from "./LinkAttachment";
|
||||
import { UnlinkAttachment } from "./UnlinkAttachment";
|
||||
import { ValidateAttachments } from "./ValidateAttachments";
|
||||
import { AttachmentsOnBillPayments } from "./events/AttachmentsOnPaymentsMade";
|
||||
import { AttachmentsOnBills } from "./events/AttachmentsOnBills";
|
||||
import { AttachmentsOnCreditNote } from "./events/AttachmentsOnCreditNote";
|
||||
import { AttachmentsOnExpenses } from "./events/AttachmentsOnExpenses";
|
||||
import { AttachmentsOnPaymentsReceived } from "./events/AttachmentsOnPaymentsReceived";
|
||||
import { AttachmentsOnManualJournals } from "./events/AttachmentsOnManualJournals";
|
||||
import { AttachmentsOnVendorCredits } from "./events/AttachmentsOnVendorCredits";
|
||||
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [S3Module],
|
||||
providers: [
|
||||
DeleteAttachment,
|
||||
GetAttachment,
|
||||
getAttachmentPresignedUrl,
|
||||
LinkAttachment,
|
||||
UnlinkAttachment,
|
||||
ValidateAttachments,
|
||||
AttachmentsOnBillPayments,
|
||||
AttachmentsOnBills,
|
||||
AttachmentsOnCreditNote,
|
||||
AttachmentsOnExpenses,
|
||||
AttachmentsOnPaymentsReceived,
|
||||
AttachmentsOnManualJournals,
|
||||
AttachmentsOnVendorCredits
|
||||
]
|
||||
})
|
||||
export class AttachmentsModule {}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Transformer } from "../Transformer/Transformer";
|
||||
|
||||
export class AttachmentTransformer extends Transformer{
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['id', 'createdAt'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UploadDocument } from './UploadDocument';
|
||||
import { DeleteAttachment } from './DeleteAttachment';
|
||||
import { GetAttachment } from './GetAttachment';
|
||||
import { LinkAttachment } from './LinkAttachment';
|
||||
import { UnlinkAttachment } from './UnlinkAttachment';
|
||||
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentsApplication {
|
||||
constructor(
|
||||
private readonly uploadDocumentService: UploadDocument,
|
||||
private readonly deleteDocumentService: DeleteAttachment,
|
||||
private readonly getDocumentService: GetAttachment,
|
||||
private readonly linkDocumentService: LinkAttachment,
|
||||
private readonly unlinkDocumentService: UnlinkAttachment,
|
||||
private readonly getPresignedUrlService: getAttachmentPresignedUrl,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Saves the metadata of uploaded document to S3 on database.
|
||||
* @param {number} tenantId
|
||||
* @param {} file
|
||||
* @returns {Promise<Document>}
|
||||
*/
|
||||
public upload(file: any) {
|
||||
return this.uploadDocumentService.upload(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the give file attachment file key.
|
||||
* @param {string} documentKey
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public delete(documentKey: string) {
|
||||
return this.deleteDocumentService.delete(documentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the document data.
|
||||
* @param {number} tenantId
|
||||
* @param {string} documentKey
|
||||
*/
|
||||
public get(documentKey: string) {
|
||||
return this.getDocumentService.getAttachment(documentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the given document to resource model.
|
||||
* @param {string} filekey
|
||||
* @param {string} modelRef
|
||||
* @param {number} modelId
|
||||
* @returns
|
||||
*/
|
||||
public link(filekey: string, modelRef: string, modelId: number) {
|
||||
return this.linkDocumentService.link(filekey, modelRef, modelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks the given document from resource model.
|
||||
* @param {number} tenantId
|
||||
* @param {string} filekey
|
||||
* @param {string} modelRef
|
||||
* @param {number} modelId
|
||||
* @returns
|
||||
*/
|
||||
public unlink(filekey: string, modelRef: string, modelId: number) {
|
||||
return this.unlinkDocumentService.unlink(filekey, modelRef, modelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the presigned url of the given attachment key.
|
||||
* @param {number} tenantId
|
||||
* @param {string} key
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public getPresignedUrl(key: string): Promise<string> {
|
||||
return this.getPresignedUrlService.getPresignedUrl(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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 { S3_CLIENT } from '../S3/S3.module';
|
||||
import { DocumentModel } from './models/Document.model';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { DocumentLinkModel } from './models/DocumentLink.model';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteAttachment {
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly configService: ConfigService,
|
||||
|
||||
@Inject(S3_CLIENT)
|
||||
private readonly s3Client: S3Client,
|
||||
|
||||
@Inject(Document.name)
|
||||
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
|
||||
|
||||
@Inject(DocumentLinkModel.name)
|
||||
private readonly documentLinkModel: TenantModelProxy<typeof DocumentLinkModel>
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the give file attachment file key.
|
||||
* @param {string} filekey
|
||||
*/
|
||||
async delete(filekey: string): Promise<void> {
|
||||
const params = {
|
||||
Bucket: this.configService.get('s3.bucket'),
|
||||
Key: filekey,
|
||||
};
|
||||
await this.s3Client.send(new DeleteObjectCommand(params));
|
||||
|
||||
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)
|
||||
.where('documentId', foundDocument.id)
|
||||
.delete();
|
||||
|
||||
// Delete thedocument.
|
||||
await this.documentModel().query(trx).findById(foundDocument.id).delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { S3_CLIENT } from '../S3/S3.module';
|
||||
|
||||
@Injectable()
|
||||
export class GetAttachment {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
|
||||
@Inject(S3_CLIENT)
|
||||
private readonly s3: S3Client
|
||||
) {
|
||||
|
||||
}
|
||||
/**
|
||||
* Retrieves data of the given document key.
|
||||
* @param {number} tenantId
|
||||
* @param {string} filekey
|
||||
*/
|
||||
async getAttachment(filekey: string) {
|
||||
const params = {
|
||||
Bucket: this.configService.get('s3.bucket'),
|
||||
Key: filekey,
|
||||
};
|
||||
const data = await this.s3.send(new GetObjectCommand(params));
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { DocumentModel } from './models/Document.model';
|
||||
|
||||
@Injectable()
|
||||
export class getAttachmentPresignedUrl {
|
||||
constructor(
|
||||
private readonly documentModel: TenantModelProxy<typeof DocumentModel>
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the presigned url of the given attachment key with the original filename.
|
||||
* @param {number} tenantId
|
||||
* @param {string} key
|
||||
* @returns {string}
|
||||
*/
|
||||
async getPresignedUrl(tenantId: number, key: string) {
|
||||
const foundDocument = await this.documentModel().query().findOne({ key });
|
||||
|
||||
let ResponseContentDisposition = 'attachment';
|
||||
if (foundDocument && foundDocument.originName) {
|
||||
ResponseContentDisposition += `; filename="${foundDocument.originName}"`;
|
||||
}
|
||||
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: config.s3.bucket,
|
||||
Key: key,
|
||||
ResponseContentDisposition,
|
||||
});
|
||||
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
|
||||
|
||||
return signedUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import bluebird from 'bluebird';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
validateLinkModelEntryExists,
|
||||
validateLinkModelExists,
|
||||
} from './Attachments/_utils';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export class LinkAttachment {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Links the given file key to the given model type and id.
|
||||
* @param {number} tenantId
|
||||
* @param {string} filekey
|
||||
* @param {string} modelRef
|
||||
* @param {number} modelId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async link(
|
||||
tenantId: number,
|
||||
filekey: string,
|
||||
modelRef: string,
|
||||
modelId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId);
|
||||
const LinkModel = models[modelRef];
|
||||
validateLinkModelExists(LinkModel);
|
||||
|
||||
const foundFile = await Document.query(trx)
|
||||
.findOne('key', filekey)
|
||||
.throwIfNotFound();
|
||||
|
||||
const foundLinkModel = await LinkModel.query(trx).findById(modelId);
|
||||
validateLinkModelEntryExists(foundLinkModel);
|
||||
|
||||
const foundLinks = await DocumentLink.query(trx)
|
||||
.where('modelRef', modelRef)
|
||||
.where('modelId', modelId)
|
||||
.where('documentId', foundFile.id);
|
||||
|
||||
if (foundLinks.length > 0) {
|
||||
throw new ServiceError(ERRORS.DOCUMENT_LINK_ALREADY_LINKED);
|
||||
}
|
||||
await DocumentLink.query(trx).insert({
|
||||
modelRef,
|
||||
modelId,
|
||||
documentId: foundFile.id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async bulkLink(
|
||||
tenantId: number,
|
||||
filekeys: string[],
|
||||
modelRef: string,
|
||||
modelId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
return bluebird.each(filekeys, async (fieldKey: string) => {
|
||||
try {
|
||||
await this.link(tenantId, fieldKey, modelRef, modelId, trx);
|
||||
} catch {
|
||||
// Ignore catching exceptions in bulk action.
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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';
|
||||
|
||||
@Service()
|
||||
export class AttachmentUploadPipeline {
|
||||
/**
|
||||
* 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.
|
||||
* @param req The HTTP request object.
|
||||
* @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) {
|
||||
if (
|
||||
!config.s3.region ||
|
||||
!config.s3.accessKeyId ||
|
||||
!config.s3.secretAccessKey
|
||||
) {
|
||||
const missingKeys = [];
|
||||
if (!config.s3.region) missingKeys.push('region');
|
||||
if (!config.s3.accessKeyId) missingKeys.push('accessKeyId');
|
||||
if (!config.s3.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
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
122
packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts
Normal file
122
packages/server-nest/src/modules/Attachments/UnlinkAttachment.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import bluebird from 'bluebird';
|
||||
import {
|
||||
validateLinkModelEntryExists,
|
||||
validateLinkModelExists,
|
||||
} from './_utils';
|
||||
import { Knex } from 'knex';
|
||||
import { difference } from 'lodash';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class UnlinkAttachment {
|
||||
/**
|
||||
* Unlink the attachments from the model entry.
|
||||
* @param {number} tenantId
|
||||
* @param {string} filekey
|
||||
* @param {string} modelRef
|
||||
* @param {number} modelId
|
||||
*/
|
||||
async unlink(
|
||||
tenantId: number,
|
||||
filekey: string,
|
||||
modelRef: string,
|
||||
modelId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId);
|
||||
|
||||
const LinkModel = models[modelRef];
|
||||
validateLinkModelExists(LinkModel);
|
||||
|
||||
const foundLinkModel = await LinkModel.query(trx).findById(modelId);
|
||||
validateLinkModelEntryExists(foundLinkModel);
|
||||
|
||||
const document = await Document.query(trx).findOne('key', filekey);
|
||||
|
||||
// Delete the document link.
|
||||
await DocumentLink.query(trx)
|
||||
.where('modelRef', modelRef)
|
||||
.where('modelId', modelId)
|
||||
.where('documentId', document.id)
|
||||
.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
): Promise<void> {
|
||||
await bluebird.each(filekeys, (fieldKey: string) => {
|
||||
try {
|
||||
this.unlink(tenantId, fieldKey, modelRef, modelId, trx);
|
||||
} catch {
|
||||
// Ignore catching exceptions on bulk action.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all the unpresented keys of the given model type and id.
|
||||
* @param {number} tenantId
|
||||
* @param {string[]} presentedKeys
|
||||
* @param {string} modelRef
|
||||
* @param {number} modelId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
async unlinkUnpresentedKeys(
|
||||
tenantId: number,
|
||||
presentedKeys: string[],
|
||||
modelRef: string,
|
||||
modelId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const { DocumentLink } = this.tenancy.models(tenantId);
|
||||
|
||||
const modelLinks = await DocumentLink.query(trx)
|
||||
.where('modelRef', modelRef)
|
||||
.where('modelId', modelId)
|
||||
.withGraphFetched('document');
|
||||
|
||||
const modelLinkKeys = modelLinks.map((link) => link.document.key);
|
||||
const unpresentedKeys = difference(modelLinkKeys, presentedKeys);
|
||||
|
||||
await this.bulkUnlink(tenantId, 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
|
||||
): Promise<void> {
|
||||
const { DocumentLink } = this.tenancy.models(tenantId);
|
||||
|
||||
// Get all the keys of the modelRef and modelId.
|
||||
const modelLinks = await DocumentLink.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { DocumentModel } from './models/Document.model';
|
||||
|
||||
@Injectable()
|
||||
export class UploadDocument {
|
||||
constructor(
|
||||
@Inject(DocumentModel.name)
|
||||
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Inserts the document metadata.
|
||||
* @param {number} tenantId
|
||||
* @param {} file
|
||||
* @returns {}
|
||||
*/
|
||||
async upload(file: any) {
|
||||
const insertedDocument = await this.documentModel().query().insert({
|
||||
key: file.key,
|
||||
mimeType: file.mimetype,
|
||||
size: file.size,
|
||||
originName: file.originalname,
|
||||
});
|
||||
return insertedDocument;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { castArray, difference } from 'lodash';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { DocumentModel } from './models/Document.model';
|
||||
|
||||
@Service()
|
||||
export class ValidateAttachments {
|
||||
|
||||
constructor(
|
||||
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[]) {
|
||||
const keys = castArray(key);
|
||||
const documents = await this.documentModel().query().whereIn('key', key);
|
||||
const documentKeys = documents.map((document) => document.key);
|
||||
|
||||
const notFoundKeys = difference(keys, documentKeys);
|
||||
|
||||
if (notFoundKeys.length > 0) {
|
||||
throw new ServiceError('DOCUMENT_KEYS_INVALID');
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/server-nest/src/modules/Attachments/_utils.ts
Normal file
14
packages/server-nest/src/modules/Attachments/_utils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
export const validateLinkModelExists = (LinkModel) => {
|
||||
if (!LinkModel) {
|
||||
throw new ServiceError(ERRORS.DOCUMENT_LINK_REF_INVALID);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateLinkModelEntryExists = (foundLinkModel) => {
|
||||
if (!foundLinkModel) {
|
||||
throw new ServiceError(ERRORS.DOCUMENT_LINK_ID_INVALID);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum ERRORS {
|
||||
DOCUMENT_LINK_REF_INVALID = 'DOCUMENT_LINK_REF_INVALID',
|
||||
DOCUMENT_LINK_ID_INVALID = 'DOCUMENT_LINK_ID_INVALID',
|
||||
DOCUMENT_LINK_ALREADY_LINKED = 'DOCUMENT_LINK_ALREADY_LINKED'
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
export class AttachmentsOnBills {
|
||||
@Inject()
|
||||
private linkAttachmentService: LinkAttachment;
|
||||
|
||||
@Inject()
|
||||
private unlinkAttachmentService: UnlinkAttachment;
|
||||
|
||||
@Inject()
|
||||
private validateDocuments: ValidateAttachments;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating bill.
|
||||
* @param {ISaleInvoiceCreatingPaylaod}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created bill.
|
||||
* @param {ISaleInvoiceCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnBillCreated({
|
||||
tenantId,
|
||||
bill,
|
||||
billDTO,
|
||||
trx,
|
||||
}: IBillCreatedPayload): Promise<void> {
|
||||
if (isEmpty(billDTO.attachments)) return;
|
||||
|
||||
const keys = billDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'Bill',
|
||||
bill.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited bill.
|
||||
* @param {IBillEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnBillEdited({
|
||||
tenantId,
|
||||
billDTO,
|
||||
bill,
|
||||
trx
|
||||
}: IBillEditedPayload) {
|
||||
const keys = billDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'Bill',
|
||||
bill.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited bill.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnBillEdited({
|
||||
tenantId,
|
||||
billDTO,
|
||||
oldBill,
|
||||
trx,
|
||||
}: IBillEditedPayload) {
|
||||
if (isEmpty(billDTO.attachments)) return;
|
||||
|
||||
const keys = billDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'Bill',
|
||||
oldBill.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the bill deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnBillDeleted({
|
||||
tenantId,
|
||||
oldBill,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'Bill',
|
||||
oldBill.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
export class AttachmentsOnCreditNote {
|
||||
@Inject()
|
||||
private linkAttachmentService: LinkAttachment;
|
||||
|
||||
@Inject()
|
||||
private unlinkAttachmentService: UnlinkAttachment;
|
||||
|
||||
@Inject()
|
||||
private validateDocuments: ValidateAttachments;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating credit note.
|
||||
* @param {ICreditNoteCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created credit note.
|
||||
* @param {ICreditNoteCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnCreditNoteCreated({
|
||||
tenantId,
|
||||
creditNote,
|
||||
creditNoteDTO,
|
||||
trx,
|
||||
}: ICreditNoteCreatedPayload): Promise<void> {
|
||||
if (isEmpty(creditNoteDTO.attachments)) return;
|
||||
|
||||
const keys = creditNoteDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'CreditNote',
|
||||
creditNote.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited credit note.
|
||||
* @param {ICreditNoteEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnCreditNoteEdited({
|
||||
tenantId,
|
||||
creditNoteEditDTO,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
}: ICreditNoteEditedPayload) {
|
||||
const keys = creditNoteEditDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'CreditNote',
|
||||
oldCreditNote.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited credit note.
|
||||
* @param {ICreditNoteEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnCreditNoteEdited({
|
||||
tenantId,
|
||||
creditNoteEditDTO,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
}: ICreditNoteEditedPayload) {
|
||||
if (isEmpty(creditNoteEditDTO.attachments)) return;
|
||||
|
||||
const keys = creditNoteEditDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'CreditNote',
|
||||
oldCreditNote.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the credit note deleted.
|
||||
* @param {ICreditNoteDeletingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnCreditNoteDeleted({
|
||||
tenantId,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
}: ICreditNoteDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'CreditNote',
|
||||
oldCreditNote.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
IExpenseCreatedPayload,
|
||||
IExpenseCreatingPayload,
|
||||
IExpenseDeletingPayload,
|
||||
IExpenseEventEditPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { LinkAttachment } from '../LinkAttachment';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating expense.
|
||||
* @param {ISaleInvoiceCreatingPaylaod}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created expense.
|
||||
* @param {ISaleInvoiceCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnExpenseCreated({
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
expense,
|
||||
trx,
|
||||
}: IExpenseCreatedPayload): Promise<void> {
|
||||
if (isEmpty(expenseDTO.attachments)) return;
|
||||
|
||||
const keys = expenseDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'Expense',
|
||||
expense.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited expense.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnExpenseEdited({
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
expense,
|
||||
trx,
|
||||
}: IExpenseEventEditPayload) {
|
||||
const keys = expenseDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'Expense',
|
||||
expense.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited expense.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnExpenseEdited({
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
oldExpense,
|
||||
trx,
|
||||
}: IExpenseEventEditPayload) {
|
||||
if (isEmpty(expenseDTO.attachments)) return;
|
||||
|
||||
const keys = expenseDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'Expense',
|
||||
oldExpense.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the expense deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnExpenseDeleted({
|
||||
tenantId,
|
||||
oldExpense,
|
||||
trx,
|
||||
}: IExpenseDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'Expense',
|
||||
oldExpense.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating manual journal.
|
||||
* @param {IManualJournalCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created manual journal.
|
||||
* @param {IManualJournalEventCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnManualJournalCreated({
|
||||
tenantId,
|
||||
manualJournalDTO,
|
||||
manualJournal,
|
||||
trx,
|
||||
}: IManualJournalEventCreatedPayload): Promise<void> {
|
||||
if (isEmpty(manualJournalDTO.attachments)) return;
|
||||
|
||||
const keys = manualJournalDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'ManualJournal',
|
||||
manualJournal.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited manual journal.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnManualJournalEdited({
|
||||
tenantId,
|
||||
manualJournalDTO,
|
||||
manualJournal,
|
||||
trx
|
||||
}: IManualJournalEventEditedPayload) {
|
||||
const keys = manualJournalDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleInvoice',
|
||||
manualJournal.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited manual journal.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnManualJournalEdited({
|
||||
tenantId,
|
||||
manualJournalDTO,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
}: IManualJournalEventEditedPayload) {
|
||||
if (isEmpty(manualJournalDTO.attachments)) return;
|
||||
|
||||
const keys = manualJournalDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'ManualJournal',
|
||||
oldManualJournal.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the manual journal deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnManualJournalDeleted({
|
||||
tenantId,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
}: IManualJournalEventDeletedPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'SaleInvoice',
|
||||
oldManualJournal.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating bill payment.
|
||||
* @param {IBillPaymentCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created bill payment.
|
||||
* @param {IBillPaymentEventCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnBillPaymentCreated({
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
billPayment,
|
||||
trx,
|
||||
}: IBillPaymentEventCreatedPayload): Promise<void> {
|
||||
if (isEmpty(billPaymentDTO.attachments)) return;
|
||||
|
||||
const keys = billPaymentDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'BillPayment',
|
||||
billPayment.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited bill payment.
|
||||
* @param {IBillPaymentEventEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnBillPaymentEdited({
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
}: IBillPaymentEventEditedPayload) {
|
||||
const keys = billPaymentDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'BillPayment',
|
||||
oldBillPayment.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited bill payment.
|
||||
* @param {IBillPaymentEventEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnBillPaymentEdited({
|
||||
tenantId,
|
||||
billPaymentDTO,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
}: IBillPaymentEventEditedPayload) {
|
||||
if (isEmpty(billPaymentDTO.attachments)) return;
|
||||
|
||||
const keys = billPaymentDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'BillPayment',
|
||||
oldBillPayment.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the bill payment deleted.
|
||||
* @param {IBillPaymentDeletingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnBillPaymentDeleted({
|
||||
tenantId,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
}: IBillPaymentDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'BillPayment',
|
||||
oldBillPayment.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating payment.
|
||||
* @param {IPaymentReceivedCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created payment.
|
||||
* @param {IPaymentReceivedCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnPaymentCreated({
|
||||
tenantId,
|
||||
paymentReceiveDTO,
|
||||
paymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedCreatedPayload): Promise<void> {
|
||||
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
||||
|
||||
const keys = paymentReceiveDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'PaymentReceive',
|
||||
paymentReceive.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited payment.
|
||||
* @param {IPaymentReceivedEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnPaymentEdited({
|
||||
tenantId,
|
||||
paymentReceiveDTO,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedEditedPayload) {
|
||||
const keys = paymentReceiveDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'PaymentReceive',
|
||||
oldPaymentReceive.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited payment.
|
||||
* @param {IPaymentReceivedEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnPaymentEdited({
|
||||
tenantId,
|
||||
paymentReceiveDTO,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedEditedPayload) {
|
||||
if (isEmpty(paymentReceiveDTO.attachments)) return;
|
||||
|
||||
const keys = paymentReceiveDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'PaymentReceive',
|
||||
oldPaymentReceive.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the payment deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnPaymentDelete({
|
||||
tenantId,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
}: IPaymentReceivedDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'PaymentReceive',
|
||||
oldPaymentReceive.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating sale estimate.
|
||||
* @param {ISaleEstimateCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created sale estimate.
|
||||
* @param {ISaleEstimateCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnSaleEstimateCreated({
|
||||
tenantId,
|
||||
saleEstimateDTO,
|
||||
saleEstimate,
|
||||
trx,
|
||||
}: ISaleEstimateCreatedPayload): Promise<void> {
|
||||
if (isEmpty(saleEstimateDTO.attachments)) return;
|
||||
|
||||
const keys = saleEstimateDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleEstimate',
|
||||
saleEstimate.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited sale estimate.
|
||||
* @param {ISaleEstimateEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnSaleEstimateEdited({
|
||||
tenantId,
|
||||
estimateDTO,
|
||||
oldSaleEstimate,
|
||||
trx
|
||||
}: ISaleEstimateEditedPayload) {
|
||||
const keys = estimateDTO.attachments?.map((attachment) => attachment.key);
|
||||
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleEstimate',
|
||||
oldSaleEstimate.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited sale estimate.
|
||||
* @param {ISaleEstimateEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnSaleEstimateEdited({
|
||||
tenantId,
|
||||
estimateDTO,
|
||||
oldSaleEstimate,
|
||||
trx,
|
||||
}: ISaleEstimateEditedPayload) {
|
||||
if (isEmpty(estimateDTO.attachments)) return;
|
||||
|
||||
const keys = estimateDTO.attachments?.map((attachment) => attachment.key);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleEstimate',
|
||||
oldSaleEstimate.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the estimate deleted.
|
||||
* @param {ISaleEstimateDeletingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnSaleEstimateDelete({
|
||||
tenantId,
|
||||
oldSaleEstimate,
|
||||
trx,
|
||||
}: ISaleEstimateDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'SaleEstimate',
|
||||
oldSaleEstimate.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceCreatingPaylaod,
|
||||
ISaleInvoiceDeletingPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { LinkAttachment } from '../LinkAttachment';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating sale invoice.
|
||||
* @param {ISaleInvoiceCreatingPaylaod}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created sale invoice.
|
||||
* @param {ISaleInvoiceCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnSaleInvoiceCreated({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
saleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload): Promise<void> {
|
||||
if (isEmpty(saleInvoiceDTO.attachments)) return;
|
||||
|
||||
const keys = saleInvoiceDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleInvoice',
|
||||
saleInvoice.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited sale invoice.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnInvoiceEdited({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
saleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) {
|
||||
// if (isEmpty(saleInvoiceDTO.attachments)) return;
|
||||
|
||||
const keys = saleInvoiceDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleInvoice',
|
||||
saleInvoice.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited sale invoice.
|
||||
* @param {ISaleInvoiceEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnInvoiceEdited({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) {
|
||||
if (isEmpty(saleInvoiceDTO.attachments)) return;
|
||||
|
||||
const keys = saleInvoiceDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleInvoice',
|
||||
oldSaleInvoice.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the invoice deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnInvoiceDeleted({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'SaleInvoice',
|
||||
oldSaleInvoice.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
ISaleReceiptCreatedPayload,
|
||||
ISaleReceiptCreatingPayload,
|
||||
ISaleReceiptDeletingPayload,
|
||||
ISaleReceiptEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { LinkAttachment } from '../LinkAttachment';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating sale receipt.
|
||||
* @param {ISaleReceiptCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created sale receipt.
|
||||
* @param {ISaleReceiptCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnSaleInvoiceCreated({
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptCreatedPayload): Promise<void> {
|
||||
if (isEmpty(saleReceiptDTO.attachments)) return;
|
||||
|
||||
const keys = saleReceiptDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleReceipt',
|
||||
saleReceipt.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited sale receipt.
|
||||
* @param {ISaleReceiptEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnInvoiceEdited({
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptEditedPayload) {
|
||||
const keys = saleReceiptDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleReceipt',
|
||||
saleReceipt.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited sale receipt.
|
||||
* @param {ISaleReceiptEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnInvoiceEdited({
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
oldSaleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptEditedPayload) {
|
||||
if (isEmpty(saleReceiptDTO.attachments)) return;
|
||||
|
||||
const keys = saleReceiptDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'SaleReceipt',
|
||||
oldSaleReceipt.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the receipt deleted.
|
||||
* @param {ISaleReceiptDeletingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnReceiptDeleted({
|
||||
tenantId,
|
||||
oldSaleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'SaleReceipt',
|
||||
oldSaleReceipt.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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';
|
||||
import { ValidateAttachments } from '../ValidateAttachments';
|
||||
import { UnlinkAttachment } from '../UnlinkAttachment';
|
||||
|
||||
@Service()
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment keys on creating vendor credit.
|
||||
* @param {IVendorCreditCreatingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking the attachments of the created vendor credit.
|
||||
* @param {IVendorCreditCreatedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleAttachmentsOnVendorCreditCreated({
|
||||
tenantId,
|
||||
vendorCreditCreateDTO,
|
||||
vendorCredit,
|
||||
trx,
|
||||
}: IVendorCreditCreatedPayload): Promise<void> {
|
||||
if (isEmpty(vendorCreditCreateDTO.attachments)) return;
|
||||
|
||||
const keys = vendorCreditCreateDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'VendorCredit',
|
||||
vendorCredit.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unlinking all the unpresented keys of the edited vendor credit.
|
||||
* @param {IVendorCreditEditedPayload}
|
||||
*/
|
||||
private async handleUnlinkUnpresentedKeysOnVendorCreditEdited({
|
||||
tenantId,
|
||||
vendorCreditDTO,
|
||||
oldVendorCredit,
|
||||
trx,
|
||||
}: IVendorCreditEditedPayload) {
|
||||
const keys = vendorCreditDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.unlinkAttachmentService.unlinkUnpresentedKeys(
|
||||
tenantId,
|
||||
keys,
|
||||
'VendorCredit',
|
||||
oldVendorCredit.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles linking all the presented keys of the edited vendor credit.
|
||||
* @param {IVendorCreditEditedPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleLinkPresentedKeysOnVendorCreditEdited({
|
||||
tenantId,
|
||||
vendorCreditDTO,
|
||||
oldVendorCredit,
|
||||
trx,
|
||||
}: IVendorCreditEditedPayload) {
|
||||
if (isEmpty(vendorCreditDTO.attachments)) return;
|
||||
|
||||
const keys = vendorCreditDTO.attachments?.map(
|
||||
(attachment) => attachment.key
|
||||
);
|
||||
await this.linkAttachmentService.bulkLink(
|
||||
tenantId,
|
||||
keys,
|
||||
'VendorCredit',
|
||||
oldVendorCredit.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink all attachments once the vendor credit deleted.
|
||||
* @param {IVendorCreditDeletingPayload}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handleUnlinkAttachmentsOnVendorCreditDeleted({
|
||||
tenantId,
|
||||
oldVendorCredit,
|
||||
trx,
|
||||
}: IVendorCreditDeletingPayload) {
|
||||
await this.unlinkAttachmentService.unlinkAllModelKeys(
|
||||
tenantId,
|
||||
'VendorCredit',
|
||||
oldVendorCredit.id,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class DocumentModel extends TenantBaseModel{
|
||||
originName!: string;
|
||||
size!: number;
|
||||
mimeType!: string;
|
||||
key!: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'documents';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Model, mixin } from 'objection';
|
||||
|
||||
export class DocumentLinkModel extends TenantBaseModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'document_links';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { DocumentModel } = require('./Document');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
document: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: DocumentModel,
|
||||
join: {
|
||||
from: 'document_links.documentId',
|
||||
to: 'documents.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
9
packages/server-nest/src/modules/Attachments/utils.ts
Normal file
9
packages/server-nest/src/modules/Attachments/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import path from 'path';
|
||||
import config from '@/config';
|
||||
|
||||
export const getUploadedObjectUri = (objectKey: string) => {
|
||||
return new URL(
|
||||
path.join(config.s3.bucket, objectKey),
|
||||
config.s3.endpoint
|
||||
).toString();
|
||||
};
|
||||
29
packages/server-nest/src/modules/S3/S3.module.ts
Normal file
29
packages/server-nest/src/modules/S3/S3.module.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { S3Client } from '@aws-sdk/client-s3';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export const S3_CLIENT = 'S3_CLIENT';
|
||||
|
||||
const services = [
|
||||
{
|
||||
provide: S3_CLIENT,
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const config = configService.get('s3');
|
||||
|
||||
return new S3Client({
|
||||
region: config.region,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
endpoint: config.endpoint,
|
||||
forcePathStyle: config.forcePathStyle,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
@Module({
|
||||
providers: [...services],
|
||||
exports: [...services],
|
||||
})
|
||||
export class S3Module {}
|
||||
Reference in New Issue
Block a user