Merge branch 'develop' into print-resources

This commit is contained in:
Ahmed Bouhuolia
2024-05-30 19:50:05 +02:00
154 changed files with 9748 additions and 6369 deletions

View File

@@ -0,0 +1,19 @@
import { Transformer } from '@/lib/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 [];
};
}

View File

@@ -0,0 +1,118 @@
import { Inject, Service } from 'typedi';
import { UploadDocument } from './UploadDocument';
import { DeleteAttachment } from './DeleteAttachment';
import { GetAttachment } from './GetAttachment';
import { AttachmentUploadPipeline } from './S3UploadPipeline';
import { LinkAttachment } from './LinkAttachment';
import { UnlinkAttachment } from './UnlinkAttachment';
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
import type { Multer } from 'multer';
@Service()
export class AttachmentsApplication {
@Inject()
private uploadDocumentService: UploadDocument;
@Inject()
private deleteDocumentService: DeleteAttachment;
@Inject()
private getDocumentService: GetAttachment;
@Inject()
private uploadPipelineService: AttachmentUploadPipeline;
@Inject()
private linkDocumentService: LinkAttachment;
@Inject()
private unlinkDocumentService: UnlinkAttachment;
@Inject()
private getPresignedUrlService: getAttachmentPresignedUrl;
/**
* Express middleware for uploading attachments to an S3 bucket.
* @returns {Multer}
*/
get uploadPipeline(): Multer {
return this.uploadPipelineService.uploadPipeline();
}
/**
* Saves the metadata of uploaded document to S3 on database.
* @param {number} tenantId
* @param {} file
* @returns {Promise<Document>}
*/
public upload(tenantId: number, file: any) {
return this.uploadDocumentService.upload(tenantId, file);
}
/**
* Deletes the give file attachment file key.
* @param {number} tenantId
* @param {string} documentKey
* @returns {Promise<void>}
*/
public delete(tenantId: number, documentKey: string) {
return this.deleteDocumentService.delete(tenantId, documentKey);
}
/**
* Retrieves the document data.
* @param {number} tenantId
* @param {string} documentKey
*/
public get(tenantId: number, documentKey: string) {
return this.getDocumentService.getAttachment(tenantId, documentKey);
}
/**
* Links the given document to resource model.
* @param {number} tenantId
* @param {string} filekey
* @param {string} modelRef
* @param {number} modelId
* @returns
*/
public link(
tenantId: number,
filekey: string,
modelRef: string,
modelId: number
) {
return this.linkDocumentService.link(tenantId, 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(
tenantId: number,
filekey: string,
modelRef: string,
modelId: number
) {
return this.unlinkDocumentService.unlink(
tenantId,
filekey,
modelRef,
modelId
);
}
/**
* Retrieves the presigned url of the given attachment key.
* @param {string} key
* @returns {Promise<string>}
*/
public getPresignedUrl(key: string): Promise<string> {
return this.getPresignedUrlService.getPresignedUrl(key);
}
}

View File

@@ -0,0 +1,45 @@
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
import { Inject, Service } from 'typedi';
import { s3 } from '@/lib/S3/S3';
import HasTenancyService from '../Tenancy/TenancyService';
import config from '@/config';
import UnitOfWork from '../UnitOfWork';
import { Knex } from 'knex';
@Service()
export class DeleteAttachment {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private uow: UnitOfWork;
/**
* Deletes the give file attachment file key.
* @param {number} tenantId
* @param {string} filekey
*/
async delete(tenantId: number, filekey: string): Promise<void> {
const { Document, DocumentLink } = this.tenancy.models(tenantId);
const params = {
Bucket: config.s3.bucket,
Key: filekey,
};
await s3.send(new DeleteObjectCommand(params));
const foundDocument = await Document.query()
.findOne('key', filekey)
.throwIfNotFound();
await this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Delete all document links
await DocumentLink.query(trx)
.where('documentId', foundDocument.id)
.delete();
// Delete thedocument.
await Document.query(trx).findById(foundDocument.id).delete();
});
}
}

View File

@@ -0,0 +1,22 @@
import { Service } from 'typedi';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { s3 } from '@/lib/S3/S3';
import config from '@/config';
@Service()
export class GetAttachment {
/**
* Retrieves data of the given document key.
* @param {number} tenantId
* @param {string} filekey
*/
async getAttachment(tenantId: number, filekey: string) {
const params = {
Bucket: config.s3.bucket,
Key: filekey,
};
const data = await s3.send(new GetObjectCommand(params));
return data;
}
}

View File

@@ -0,0 +1,23 @@
import { Service } from 'typedi';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { s3 } from '@/lib/S3/S3';
import config from '@/config';
@Service()
export class getAttachmentPresignedUrl {
/**
* Retrieves the presigned url of the given attachment key.
* @param {string} key
* @returns {Promise<string?>}
*/
async getPresignedUrl(key: string) {
const command = new GetObjectCommand({
Bucket: config.s3.bucket,
Key: key,
});
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
return signedUrl;
}
}

View File

@@ -0,0 +1,82 @@
import { Inject, Service } from 'typedi';
import bluebird from 'bluebird';
import { Knex } from 'knex';
import {
validateLinkModelEntryExists,
validateLinkModelExists,
} from './_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.
}
});
}
}

View File

@@ -0,0 +1,29 @@
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 {
/**
* 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());
},
}),
});
}
}

View File

@@ -0,0 +1,125 @@
import { Inject, Service } from 'typedi';
import bluebird from 'bluebird';
import HasTenancyService from '../Tenancy/TenancyService';
import {
validateLinkModelEntryExists,
validateLinkModelExists,
} from './_utils';
import { Knex } from 'knex';
import { difference } from 'lodash';
@Service()
export class UnlinkAttachment {
@Inject()
private tenancy: HasTenancyService;
/**
* 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);
}
}

View File

@@ -0,0 +1,26 @@
import { Inject, Service } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService';
@Service()
export class UploadDocument {
@Inject()
private tenancy: HasTenancyService;
/**
* Inserts the document metadata.
* @param {number} tenantId
* @param {} file
* @returns {}
*/
async upload(tenantId: number, file: any) {
const { Document } = this.tenancy.models(tenantId);
const insertedDocument = await Document.query().insert({
key: file.key,
mimeType: file.mimetype,
size: file.size,
originName: file.originalname,
});
return insertedDocument;
}
}

View File

@@ -0,0 +1,29 @@
import { castArray, difference } from 'lodash';
import HasTenancyService from '../Tenancy/TenancyService';
import { ServiceError } from '@/exceptions';
import { Inject, Service } from 'typedi';
@Service()
export class ValidateAttachments {
@Inject()
tenancy: HasTenancyService;
/**
* Validates the given file keys existance.
* @param {number} tenantId
* @param {string|string[]} key
*/
async validate(tenantId: number, key: string | string[]) {
const { Document } = this.tenancy.models(tenantId);
const keys = castArray(key);
const documents = await Document.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');
}
}
}

View 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);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,157 @@
import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
import {
IPaymentReceiveCreatedPayload,
IPaymentReceiveCreatingPayload,
IPaymentReceiveDeletingPayload,
IPaymentReceiveEditedPayload,
} 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 {IPaymentReceiveCreatingPayload}
* @returns {Promise<void>}
*/
private async validateAttachmentsOnPaymentCreate({
paymentReceiveDTO,
tenantId,
}: IPaymentReceiveCreatingPayload): 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 {IPaymentReceiveCreatedPayload}
* @returns {Promise<void>}
*/
private async handleAttachmentsOnPaymentCreated({
tenantId,
paymentReceiveDTO,
paymentReceive,
trx,
}: IPaymentReceiveCreatedPayload): 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 {IPaymentReceiveEditedPayload}
*/
private async handleUnlinkUnpresentedKeysOnPaymentEdited({
tenantId,
paymentReceiveDTO,
oldPaymentReceive,
trx,
}: IPaymentReceiveEditedPayload) {
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 {IPaymentReceiveEditedPayload}
* @returns {Promise<void>}
*/
private async handleLinkPresentedKeysOnPaymentEdited({
tenantId,
paymentReceiveDTO,
oldPaymentReceive,
trx,
}: IPaymentReceiveEditedPayload) {
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,
}: IPaymentReceiveDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'PaymentReceive',
oldPaymentReceive.id,
trx
);
}
}

View File

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

View File

@@ -0,0 +1,159 @@
import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
import {
ISaleInvoiceCreatedPayload,
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceDeletePayload,
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,
saleInvoice,
trx,
}: ISaleInvoiceDeletePayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleInvoice',
saleInvoice.id,
trx
);
}
}

View File

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

View File

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

View File

@@ -20,6 +20,10 @@ export class SendVerfiyMailOnSignUp {
private handleSendVerifyMailOnSignup = async ({
user,
}: IAuthSignedUpEventPayload) => {
// Can't continue if the user is verified.
if (user.verified) {
return;
}
const payload = {
email: user.email,
token: user.verifyToken,

View File

@@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { ItemEntryTransformer } from '../Sales/Invoices/ItemEntryTransformer';
import { ICreditNote } from '@/interfaces';
import { AttachmentTransformer } from '../Attachments/AttachmentTransformer';
export class CreditNoteTransformer extends Transformer {
/**
@@ -16,6 +17,7 @@ export class CreditNoteTransformer extends Transformer {
'formattedCreditsUsed',
'formattedSubtotal',
'entries',
'attachments',
];
};
@@ -80,4 +82,13 @@ export class CreditNoteTransformer extends Transformer {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the credit note attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (creditNote) => {
return this.item(creditNote.attachments, new AttachmentTransformer());
};
}

View File

@@ -57,7 +57,7 @@ export default class BaseCreditNotes {
autoNextNumber;
const initialDTO = {
...omit(creditNoteDTO, ['open']),
...omit(creditNoteDTO, ['open', 'attachments']),
creditNoteNumber,
amount,
currencyCode: customerCurrencyCode,

View File

@@ -28,7 +28,8 @@ export default class GetCreditNote extends BaseCreditNotes {
.findById(creditNoteId)
.withGraphFetched('entries.item')
.withGraphFetched('customer')
.withGraphFetched('branch');
.withGraphFetched('branch')
.withGraphFetched('attachments');
if (!creditNote) {
throw new ServiceError(ERRORS.CREDIT_NOTE_NOT_FOUND);

View File

@@ -123,6 +123,7 @@ export class CreateExpense {
tenantId,
expenseId: expense.id,
authorizedUser,
expenseDTO,
expense,
trx,
} as IExpenseCreatedPayload);

View File

@@ -54,7 +54,7 @@ export class ExpenseDTOTransformer {
const initialDTO = {
categories: [],
...omit(expenseDTO, ['publish']),
...omit(expenseDTO, ['publish', 'attachments']),
totalAmount,
landedCostAmount,
paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(),

View File

@@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { IExpense } from '@/interfaces';
import { ExpenseCategoryTransformer } from './ExpenseCategoryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class ExpenseTransfromer extends Transformer {
/**
@@ -15,6 +16,7 @@ export class ExpenseTransfromer extends Transformer {
'formattedAllocatedCostAmount',
'formattedDate',
'categories',
'attachments',
];
};
@@ -70,4 +72,13 @@ export class ExpenseTransfromer extends Transformer {
currencyCode: expense.currencyCode,
});
};
/**
* Retrieves the sale invoice attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (expense: IExpense) => {
return this.item(expense.attachments, new AttachmentTransformer());
};
}

View File

@@ -29,6 +29,7 @@ export class GetExpense {
.withGraphFetched('categories.expenseAccount')
.withGraphFetched('paymentAccount')
.withGraphFetched('branch')
.withGraphFetched('attachments')
.throwIfNotFound();
// Transformes expense model to POJO.

View File

@@ -12,7 +12,7 @@ import {
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import { Tenant, TenantMetadata } from '@/system/models';
import { TenantMetadata } from '@/system/models';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { CommandManualJournalValidators } from './CommandManualJournalValidators';
@@ -59,7 +59,7 @@ export class CreateManualJournalService {
const journalNumber = manualJournalDTO.journalNumber || autoNextNumber;
const initialDTO = {
...omit(manualJournalDTO, ['publish']),
...omit(manualJournalDTO, ['publish', 'attachments']),
...(manualJournalDTO.publish
? { publishedAt: moment().toMySqlDateTime() }
: {}),
@@ -173,6 +173,7 @@ export class CreateManualJournalService {
tenantId,
manualJournal,
manualJournalId: manualJournal.id,
manualJournalDTO,
trx,
} as IManualJournalEventCreatedPayload);

View File

@@ -78,7 +78,7 @@ export class EditManualJournal {
return {
id: oldManualJournal.id,
...omit(manualJournalDTO, ['publish']),
...omit(manualJournalDTO, ['publish', 'attachments']),
...(manualJournalDTO.publish && !oldManualJournal.publishedAt
? { publishedAt: moment().toMySqlDateTime() }
: {}),
@@ -143,6 +143,7 @@ export class EditManualJournal {
tenantId,
manualJournal,
oldManualJournal,
manualJournalDTO,
trx,
} as IManualJournalEventEditedPayload);

View File

@@ -28,7 +28,7 @@ export class GetManualJournal {
.withGraphFetched('entries.contact')
.withGraphFetched('entries.branch')
.withGraphFetched('transactions')
.withGraphFetched('media')
.withGraphFetched('attachments')
.throwIfNotFound();
return this.transformer.transform(

View File

@@ -1,6 +1,7 @@
import { IManualJournal } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { AttachmentTransformer } from '../Attachments/AttachmentTransformer';
export class ManualJournalTransfromer extends Transformer {
/**
@@ -8,7 +9,12 @@ export class ManualJournalTransfromer extends Transformer {
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount', 'formattedDate', 'formattedPublishedAt'];
return [
'formattedAmount',
'formattedDate',
'formattedPublishedAt',
'attachments',
];
};
/**
@@ -39,4 +45,13 @@ export class ManualJournalTransfromer extends Transformer {
protected formattedPublishedAt = (manualJorunal: IManualJournal): string => {
return this.formatDate(manualJorunal.publishedAt);
};
/**
* Retrieves the manual journal attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (manualJorunal: IManualJournal) => {
return this.item(manualJorunal.attachments, new AttachmentTransformer());
};
}

View File

@@ -2,6 +2,7 @@ import { IBillPayment } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { BillPaymentEntryTransformer } from './BillPaymentEntryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class BillPaymentTransformer extends Transformer {
/**
@@ -9,7 +10,12 @@ export class BillPaymentTransformer extends Transformer {
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedPaymentDate', 'formattedAmount', 'entries'];
return [
'formattedPaymentDate',
'formattedAmount',
'entries',
'attachments',
];
};
/**
@@ -38,4 +44,13 @@ export class BillPaymentTransformer extends Transformer {
protected entries = (billPayment) => {
return this.item(billPayment.entries, new BillPaymentEntryTransformer());
};
/**
* Retrieves the bill attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (billPayment) => {
return this.item(billPayment.attachments, new AttachmentTransformer());
};
}

View File

@@ -25,7 +25,8 @@ export default class BillPaymentsPages {
const { BillPayment, Bill } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query()
.findById(billPaymentId)
.withGraphFetched('entries.bill');
.withGraphFetched('entries.bill')
.withGraphFetched('attachments');
// Throw not found the bill payment.
if (!billPayment) {

View File

@@ -1,6 +1,6 @@
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import { sumBy } from 'lodash';
import { omit, sumBy } from 'lodash';
import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { formatDateFields } from '@/utils';
@@ -24,7 +24,9 @@ export class CommandBillPaymentDTOTransformer {
oldBillPayment?: IBillPayment
): Promise<IBillPayment> {
const initialDTO = {
...formatDateFields(billPaymentDTO, ['paymentDate']),
...formatDateFields(omit(billPaymentDTO, ['attachments']), [
'paymentDate',
]),
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
currencyCode: vendor.currencyCode,
exchangeRate: billPaymentDTO.exchangeRate || 1,

View File

@@ -112,12 +112,12 @@ export class CreateBillPayment {
const billPayment = await BillPayment.query(trx).insertGraphAndFetch({
...billPaymentObj,
});
// Triggers `onBillPaymentCreated` event.
await this.eventPublisher.emitAsync(events.billPayment.onCreated, {
tenantId,
billPayment,
billPaymentId: billPayment.id,
billPaymentDTO,
trx,
} as IBillPaymentEventCreatedPayload);

View File

@@ -137,6 +137,7 @@ export class EditBillPayment {
billPaymentId,
billPayment,
oldBillPayment,
billPaymentDTO,
trx,
} as IBillPaymentEventEditedPayload);

View File

@@ -13,7 +13,7 @@ export class GetBillPayment {
private transformer: TransformerInjectable;
/**
* Retrieve bill payment.
* Retrieves bill payment.
* @param {number} tenantId
* @param {number} billPyamentId
* @return {Promise<IBillPayment>}
@@ -30,6 +30,7 @@ export class GetBillPayment {
.withGraphFetched('paymentAccount')
.withGraphFetched('transactions')
.withGraphFetched('branch')
.withGraphFetched('attachments')
.findById(billPyamentId)
.throwIfNotFound();

View File

@@ -96,7 +96,7 @@ export class BillDTOTransformer {
)(asyncEntries);
const initialDTO = {
...formatDateFields(omit(billDTO, ['open', 'entries']), [
...formatDateFields(omit(billDTO, ['open', 'entries', 'attachments']), [
'billDate',
'dueDate',
]),

View File

@@ -110,6 +110,7 @@ export class CreateBill {
tenantId,
bill,
billId: bill.id,
billDTO,
trx,
} as IBillCreatedPayload);

View File

@@ -148,6 +148,7 @@ export class EditBill {
billId,
oldBill,
bill,
billDTO,
trx,
} as IBillEditedPayload);

View File

@@ -29,7 +29,8 @@ export class GetBill {
.withGraphFetched('vendor')
.withGraphFetched('entries.item')
.withGraphFetched('branch')
.withGraphFetched('taxes.taxRate');
.withGraphFetched('taxes.taxRate')
.withGraphFetched('attachments');
// Validates the bill existance.
this.validators.validateBillExistance(bill);

View File

@@ -1,5 +1,6 @@
import { IBill } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
import { ItemEntryTransformer } from '@/services/Sales/Invoices/ItemEntryTransformer';
import { SaleInvoiceTaxEntryTransformer } from '@/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer';
import { formatNumber } from 'utils';
@@ -26,6 +27,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
'totalLocalFormatted',
'taxes',
'entries',
'attachments',
];
};
@@ -192,4 +194,13 @@ export class PurchaseInvoiceTransformer extends Transformer {
currencyCode: bill.currencyCode,
});
};
/**
* Retrieves the bill attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (bill) => {
return this.item(bill.attachments, new AttachmentTransformer());
};
}

View File

@@ -64,7 +64,7 @@ export default class BaseVendorCredit {
autoNextNumber;
const initialDTO = {
...omit(vendorCreditDTO, ['open']),
...omit(vendorCreditDTO, ['open', 'attachments']),
amount,
currencyCode: vendorCurrencyCode,
exchangeRate: vendorCreditDTO.exchangeRate || 1,

View File

@@ -93,6 +93,7 @@ export default class EditVendorCredit extends BaseVendorCredit {
oldVendorCredit,
vendorCredit,
vendorCreditId,
vendorCreditDTO,
trx,
} as IVendorCreditEditedPayload);

View File

@@ -26,7 +26,8 @@ export default class GetVendorCredit {
.findById(vendorCreditId)
.withGraphFetched('entries.item')
.withGraphFetched('vendor')
.withGraphFetched('branch');
.withGraphFetched('branch')
.withGraphFetched('attachments');
if (!vendorCredit) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_NOT_FOUND);

View File

@@ -1,5 +1,6 @@
import { IVendorCredit } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
import { ItemEntryTransformer } from '@/services/Sales/Invoices/ItemEntryTransformer';
import { formatNumber } from 'utils';
@@ -16,6 +17,7 @@ export class VendorCreditTransformer extends Transformer {
'formattedCreditsRemaining',
'formattedInvoicedAmount',
'entries',
'attachments',
];
};
@@ -80,4 +82,13 @@ export class VendorCreditTransformer extends Transformer {
currencyCode: vendorCredit.currencyCode,
});
};
/**
* Retrieves the vendor credit attachments.
* @param {IVendorCredit} invoice
* @returns
*/
protected attachments = (vendorCredit) => {
return this.item(vendorCredit.attachments, new AttachmentTransformer());
};
}

View File

@@ -114,6 +114,7 @@ export class EditSaleEstimate {
estimateId,
saleEstimate,
oldSaleEstimate,
estimateDTO,
trx,
} as ISaleEstimateEditedPayload);

View File

@@ -28,7 +28,8 @@ export class GetSaleEstimate {
.findById(estimateId)
.withGraphFetched('entries.item')
.withGraphFetched('customer')
.withGraphFetched('branch');
.withGraphFetched('branch')
.withGraphFetched('attachments');
// Validates the estimate existance.
this.validators.validateEstimateExistance(estimate);

View File

@@ -58,10 +58,10 @@ export class SaleEstimateDTOTransformer {
const initialDTO = {
amount,
...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [
'estimateDate',
'expirationDate',
]),
...formatDateFields(
omit(estimateDTO, ['delivered', 'entries', 'attachments']),
['estimateDate', 'expirationDate']
),
currencyCode: paymentCustomer.currencyCode,
exchangeRate: estimateDTO.exchangeRate || 1,
...(estimateNumber ? { estimateNumber } : {}),

View File

@@ -2,6 +2,7 @@ import { ISaleEstimate } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { ItemEntryTransformer } from '../Invoices/ItemEntryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class SaleEstimateTransfromer extends Transformer {
/**
@@ -18,6 +19,7 @@ export class SaleEstimateTransfromer extends Transformer {
'formattedApprovedAtDate',
'formattedRejectedAtDate',
'entries',
'attachments',
];
};
@@ -91,9 +93,18 @@ export class SaleEstimateTransfromer extends Transformer {
* @param {ISaleEstimate} estimate
* @returns {}
*/
protected entries = (estimate) => {
protected entries = (estimate: ISaleEstimate) => {
return this.item(estimate.entries, new ItemEntryTransformer(), {
currencyCode: estimate.currencyCode,
});
};
/**
* Retrieves the sale estimate attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (estimate: ISaleEstimate) => {
return this.item(estimate.attachments, new AttachmentTransformer());
};
}

View File

@@ -86,7 +86,12 @@ export class CommandSaleInvoiceDTOTransformer {
const initialDTO = {
...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
omit(saleInvoiceDTO, [
'delivered',
'entries',
'fromEstimateId',
'attachments',
]),
['invoiceDate', 'dueDate']
),
// Avoid rewrite the deliver date in edit mode when already published.

View File

@@ -34,7 +34,8 @@ export class GetSaleInvoice {
.withGraphFetched('entries.tax')
.withGraphFetched('customer')
.withGraphFetched('branch')
.withGraphFetched('taxes.taxRate');
.withGraphFetched('taxes.taxRate')
.withGraphFetched('attachments');
// Validates the given sale invoice existance.
this.validators.validateInvoiceExistance(saleInvoice);

View File

@@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer';
import { ItemEntryTransformer } from './ItemEntryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class SaleInvoiceTransformer extends Transformer {
/**
@@ -25,6 +26,7 @@ export class SaleInvoiceTransformer extends Transformer {
'totalLocalFormatted',
'taxes',
'entries',
'attachments',
];
};
@@ -190,4 +192,13 @@ export class SaleInvoiceTransformer extends Transformer {
currencyCode: invoice.currencyCode,
});
};
/**
* Retrieves the sale invoice attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (invoice) => {
return this.item(invoice.attachments, new AttachmentTransformer());
};
}

View File

@@ -110,6 +110,7 @@ export class CreatePaymentReceive {
tenantId,
paymentReceive,
paymentReceiveId: paymentReceive.id,
paymentReceiveDTO,
authorizedUser,
trx,
} as IPaymentReceiveCreatedPayload);

View File

@@ -51,7 +51,7 @@ export class PaymentReceiveDTOTransformer {
this.validators.validatePaymentNoRequire(paymentReceiveNo);
const initialDTO = {
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [
'paymentDate',
]),
amount: paymentAmount,

View File

@@ -66,7 +66,7 @@ export default class PaymentReceivesPages {
*/
public async getPaymentReceiveEditPage(
tenantId: number,
paymentReceiveId: number,
paymentReceiveId: number
): Promise<{
paymentReceive: Omit<IPaymentReceive, 'entries'>;
entries: IPaymentReceivePageEntry[];
@@ -76,7 +76,8 @@ export default class PaymentReceivesPages {
// Retrieve payment receive.
const paymentReceive = await PaymentReceive.query()
.findById(paymentReceiveId)
.withGraphFetched('entries.invoice');
.withGraphFetched('entries.invoice')
.withGraphFetched('attachments');
// Throw not found the payment receive.
if (!paymentReceive) {

View File

@@ -101,6 +101,7 @@ export class CreateSaleReceipt {
tenantId,
saleReceipt,
saleReceiptId: saleReceipt.id,
saleReceiptDTO,
trx,
} as ISaleReceiptCreatedPayload);

View File

@@ -110,6 +110,7 @@ export class EditSaleReceipt {
oldSaleReceipt,
saleReceipt,
saleReceiptId,
saleReceiptDTO,
trx,
} as ISaleReceiptEditedPayload);

View File

@@ -28,7 +28,8 @@ export class GetSaleReceipt {
.withGraphFetched('entries.item')
.withGraphFetched('customer')
.withGraphFetched('depositAccount')
.withGraphFetched('branch');
.withGraphFetched('branch')
.withGraphFetched('attachments');
// Valdiates the sale receipt existance.
this.validators.validateReceiptExistance(saleReceipt);

View File

@@ -68,9 +68,10 @@ export class SaleReceiptDTOTransformer {
const initialDTO = {
amount,
...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
'receiptDate',
]),
...formatDateFields(
omit(saleReceiptDTO, ['closed', 'entries', 'attachments']),
['receiptDate']
),
currencyCode: paymentCustomer.currencyCode,
exchangeRate: saleReceiptDTO.exchangeRate || 1,
receiptNumber,

View File

@@ -3,6 +3,7 @@ import { ISaleReceipt } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import { ItemEntryTransformer } from '../Invoices/ItemEntryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
@Service()
export class SaleReceiptTransformer extends Transformer {
@@ -17,6 +18,7 @@ export class SaleReceiptTransformer extends Transformer {
'formattedReceiptDate',
'formattedClosedAtDate',
'entries',
'attachments',
];
};
@@ -68,4 +70,13 @@ export class SaleReceiptTransformer extends Transformer {
currencyCode: receipt.currencyCode,
});
};
/**
* Retrieves the sale receipt attachments.
* @param {ISaleReceipt} invoice
* @returns
*/
protected attachments = (receipt) => {
return this.item(receipt.attachments, new AttachmentTransformer());
};
}

View File

@@ -10,6 +10,7 @@ import {
} from './utils';
import { Plan } from '@/system/models';
import { Subscription } from './Subscription';
import { isEmpty } from 'lodash';
@Service()
export class LemonSqueezyWebhooks {
@@ -32,6 +33,9 @@ export class LemonSqueezyWebhooks {
if (!config.lemonSqueezy.webhookSecret) {
throw new Error('Lemon Squeezy Webhook Secret not set in .env');
}
if (!signature) {
throw new Error('Request signature is required.');
}
const secret = config.lemonSqueezy.webhookSecret;
const hmacSignature = createHmacSignature(secret, rawData);
@@ -49,7 +53,7 @@ export class LemonSqueezyWebhooks {
/**
* This action will process a webhook event in the database.
* @param {unknown} eventBody -
* @param {unknown} eventBody -
* @returns {Promise<void>}
*/
private async processWebhookEvent(eventBody): Promise<void> {
@@ -96,7 +100,7 @@ export class LemonSqueezyWebhooks {
if (webhookEvent === 'subscription_created') {
await this.subscriptionService.newSubscribtion(
tenantId,
'early-adaptor',
'early-adaptor'
);
}
}