refactor: migrate credit note and vendor credit services to nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-29 18:37:33 +02:00
parent 9f9b75cd31
commit caf235e2b5
107 changed files with 7396 additions and 109 deletions

View File

@@ -0,0 +1,53 @@
import { Transformer } from '../../Transformer/Transformer';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
export class CreditNoteAppliedInvoiceTransformer extends Transformer {
/**
* Includeded attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formttedAmount',
'creditNoteNumber',
'creditNoteDate',
'invoiceNumber',
'invoiceReferenceNo',
'formattedCreditNoteDate',
];
};
/**
* Exclude attributes.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return ['saleInvoice', 'creditNote'];
};
public formttedAmount = (item: CreditNoteAppliedInvoice) => {
return this.formatNumber(item.amount, {
currencyCode: item.creditNote.currencyCode,
});
};
public creditNoteNumber = (item: CreditNoteAppliedInvoice) => {
return item.creditNote.creditNoteNumber;
};
public creditNoteDate = (item: CreditNoteAppliedInvoice) => {
return item.creditNote.creditNoteDate;
};
public invoiceNumber = (item: CreditNoteAppliedInvoice) => {
return item.saleInvoice.invoiceNo;
};
public invoiceReferenceNo = (item: CreditNoteAppliedInvoice) => {
return item.saleInvoice.referenceNo;
};
public formattedCreditNoteDate = (item: CreditNoteAppliedInvoice) => {
return this.formatDate(item.creditNote.creditNoteDate);
};
}

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { defaultCreditNoteBrandingAttributes } from '../constants';
import { GetPdfTemplateService } from '../../PdfTemplate/queries/GetPdfTemplate.service';
import { GetOrganizationBrandingAttributesService } from '../../PdfTemplate/queries/GetOrganizationBrandingAttributes.service';
import { mergePdfTemplateWithDefaultAttributes } from '../../SaleInvoices/utils';
@Injectable()
export class CreditNoteBrandingTemplate {
constructor(
private getPdfTemplateService: GetPdfTemplateService,
private getOrgBrandingAttributes: GetOrganizationBrandingAttributesService,
) {}
/**
* Retrieves the credit note branding template.
* @param {number} templateId
* @returns {}
*/
public async getCreditNoteBrandingTemplate(templateId: number) {
const template =
await this.getPdfTemplateService.getPdfTemplate(templateId);
// Retrieves the organization branding attributes.
const commonOrgBrandingAttrs =
await this.getOrgBrandingAttributes.getOrganizationBrandingAttributes();
// Merges the default branding attributes with common organization branding attrs.
const organizationBrandingAttrs = {
...defaultCreditNoteBrandingAttributes,
...commonOrgBrandingAttrs,
};
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes(
brandingTemplateAttrs,
organizationBrandingAttrs,
);
return {
...template,
attributes,
};
}
}

View File

@@ -0,0 +1,194 @@
import { AttachmentTransformer } from "@/modules/Attachments/Attachment.transformer";
import { ItemEntryTransformer } from "@/modules/TransactionItemEntry/ItemEntry.transformer";
import { Transformer } from "@/modules/Transformer/Transformer";
export class CreditNoteTransformer extends Transformer {
/**
* Include these attributes to sale credit note object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedCreditsRemaining',
'formattedCreditNoteDate',
'formattedCreatedAt',
'formattedCreatedAt',
'formattedAmount',
'formattedCreditsUsed',
'formattedSubtotal',
'discountAmountFormatted',
'discountAmountLocalFormatted',
'discountPercentageFormatted',
'adjustmentFormatted',
'adjustmentLocalFormatted',
'totalFormatted',
'totalLocalFormatted',
'entries',
'attachments',
];
};
/**
* Retrieve formatted credit note date.
* @param {ICreditNote} credit
* @returns {String}
*/
protected formattedCreditNoteDate = (credit): string => {
return this.formatDate(credit.creditNoteDate);
};
/**
* Retrieve formatted created at date.
* @param credit
* @returns {string}
*/
protected formattedCreatedAt = (credit): string => {
return this.formatDate(credit.createdAt);
};
/**
* Retrieve formatted invoice amount.
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedAmount = (credit): string => {
return this.formatNumber(credit.amount, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieve formatted credits remaining.
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedCreditsRemaining = (credit) => {
return this.formatNumber(credit.creditsRemaining, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieve formatted credits used.
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedCreditsUsed = (credit) => {
return this.formatNumber(credit.creditsUsed, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the formatted subtotal.
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedSubtotal = (credit): string => {
return this.formatNumber(credit.amount, { money: false });
};
/**
* Retrieves formatted discount amount.
* @param credit
* @returns {string}
*/
protected discountAmountFormatted = (credit): string => {
return this.formatNumber(credit.discountAmount, {
currencyCode: credit.currencyCode,
excerptZero: true,
});
};
/**
* Retrieves the formatted discount amount in local currency.
* @param {ICreditNote} credit
* @returns {string}
*/
protected discountAmountLocalFormatted = (credit): string => {
return this.formatNumber(credit.discountAmountLocal, {
currencyCode: credit.currencyCode,
excerptZero: true,
});
};
/**
* Retrieves formatted discount percentage.
* @param credit
* @returns {string}
*/
protected discountPercentageFormatted = (credit): string => {
return credit.discountPercentage ? `${credit.discountPercentage}%` : '';
};
/**
* Retrieves formatted adjustment amount.
* @param credit
* @returns {string}
*/
protected adjustmentFormatted = (credit): string => {
return this.formatMoney(credit.adjustment, {
currencyCode: credit.currencyCode,
excerptZero: true,
});
};
/**
* Retrieves the formatted adjustment amount in local currency.
* @param {ICreditNote} credit
* @returns {string}
*/
protected adjustmentLocalFormatted = (credit): string => {
return this.formatNumber(credit.adjustmentLocal, {
currencyCode: this.context.organization.baseCurrency,
excerptZero: true,
});
};
/**
* Retrieves the formatted total.
* @param credit
* @returns {string}
*/
protected totalFormatted = (credit): string => {
return this.formatNumber(credit.total, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the formatted total in local currency.
* @param credit
* @returns {string}
*/
protected totalLocalFormatted = (credit): string => {
return this.formatNumber(credit.totalLocal, {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the entries of the credit note.
* @param {ICreditNote} credit
* @returns {}
*/
protected entries = (credit) => {
return this.item(credit.entries, new ItemEntryTransformer(), {
currencyCode: credit.currencyCode,
});
};
/**
* Retrieves the credit note attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (creditNote) => {
return this.item(creditNote.attachments, new AttachmentTransformer());
};
}

View File

@@ -0,0 +1,39 @@
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { ERRORS } from '../constants';
import { CreditNoteTransformer } from './CreditNoteTransformer';
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '../models/CreditNote';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class GetCreditNote {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
) {}
/**
* Retrieve the credit note graph.
* @param {number} tenantId
* @param {number} creditNoteId
* @returns
*/
public async getCreditNote(creditNoteId: number) {
// Retrieve the vendor credit model graph.
const creditNote = await this.creditNoteModel
.query()
.findById(creditNoteId)
.withGraphFetched('entries.item')
.withGraphFetched('customer')
.withGraphFetched('branch')
.withGraphFetched('attachments');
if (!creditNote) {
throw new ServiceError(ERRORS.CREDIT_NOTE_NOT_FOUND);
}
// Transforms the credit note model to POJO.
return this.transformer.transform(creditNote, new CreditNoteTransformer());
}
}

View File

@@ -0,0 +1,42 @@
import { Inject, Injectable } from '@nestjs/common';
import { CreditNoteAppliedInvoiceTransformer } from './CreditNoteAppliedInvoiceTransformer';
import { CreditNote } from '../models/CreditNote';
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
@Injectable()
export class GetCreditNoteAssociatedAppliedInvoices {
constructor(
private readonly transformer: TransformerInjectable,
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote
) {
}
/**
* Retrieve credit note associated invoices to apply.
* @param {number} creditNoteId
* @returns {Promise<CreditNoteAppliedInvoice[]>}
*/
public async getCreditAssociatedAppliedInvoices(
creditNoteId: number
): Promise<CreditNoteAppliedInvoice[]> {
// Retrieve credit note or throw not found service error.
const creditNote = await this.creditNoteModel.query()
.findById(creditNoteId)
.throwIfNotFound();
const appliedToInvoices = await this.creditNoteAppliedInvoiceModel.query()
.where('credit_note_id', creditNoteId)
.withGraphFetched('saleInvoice')
.withGraphFetched('creditNote');
// Transforms credit note applied to invoices.
return this.transformer.transform(
appliedToInvoices,
new CreditNoteAppliedInvoiceTransformer()
);
}
}

View File

@@ -0,0 +1,41 @@
import { Inject, Injectable } from '@nestjs/common';
import { CreditNoteWithInvoicesToApplyTransformer } from '../commands/CreditNoteWithInvoicesToApplyTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { GetCreditNote } from './GetCreditNote.service';
@Injectable()
export class GetCreditNoteAssociatedInvoicesToApply {
constructor(
private transformer: TransformerInjectable,
private getCreditNote: GetCreditNote,
@Inject(SaleInvoice.name)
private saleInvoiceModel: typeof SaleInvoice,
) {}
/**
* Retrieve credit note associated invoices to apply.
* @param {number} creditNoteId
* @returns {Promise<ISaleInvoice[]>}
*/
public async getCreditAssociatedInvoicesToApply(
creditNoteId: number,
): Promise<SaleInvoice[]> {
// Retrieve credit note or throw not found service error.
const creditNote = await this.getCreditNote.getCreditNote(creditNoteId);
// Retrieves the published due invoices that associated to the given customer.
const saleInvoices = await this.saleInvoiceModel
.query()
.where('customerId', creditNote.customerId)
.modify('dueInvoices')
.modify('published');
// Transforms the sale invoices models to POJO.
return this.transformer.transform(
saleInvoices,
new CreditNoteWithInvoicesToApplyTransformer(),
);
}
}

View File

@@ -0,0 +1,99 @@
import { Inject, Injectable } from '@nestjs/common';
import { GetCreditNote } from './GetCreditNote.service';
import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate.service';
import { transformCreditNoteToPdfTemplate } from '../utils';
import { CreditNote } from '../models/CreditNote';
import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service';
import { TemplateInjectable } from '@/modules/TemplateInjectable/TemplateInjectable.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
import { CreditNotePdfTemplateAttributes } from '../types/CreditNotes.types';
import { events } from '@/common/events/events';
@Injectable()
export class GetCreditNotePdf {
constructor(
private readonly chromiumlyTenancy: ChromiumlyTenancy,
private readonly templateInjectable: TemplateInjectable,
private readonly getCreditNoteService: GetCreditNote,
private readonly creditNoteBrandingTemplate: CreditNoteBrandingTemplate,
private readonly eventPublisher: EventEmitter2,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
@Inject(PdfTemplateModel.name)
private readonly pdfTemplateModel: typeof PdfTemplateModel,
) {}
/**
* Retrieves sale invoice pdf content.
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<[Buffer, string]>}
*/
public async getCreditNotePdf(
creditNoteId: number,
): Promise<[Buffer, string]> {
const brandingAttributes =
await this.getCreditNoteBrandingAttributes(creditNoteId);
const htmlContent = await this.templateInjectable.render(
'modules/credit-note-standard',
brandingAttributes,
);
const filename = await this.getCreditNoteFilename(creditNoteId);
const document =
await this.chromiumlyTenancy.convertHtmlContent(htmlContent);
const eventPayload = { creditNoteId };
// Triggers the `onCreditNotePdfViewed` event.
await this.eventPublisher.emitAsync(
events.creditNote.onPdfViewed,
eventPayload,
);
return [document, filename];
}
/**
* Retrieves the filename pdf document of the given credit note.
* @param {number} creditNoteId
* @returns {Promise<string>}
*/
public async getCreditNoteFilename(creditNoteId: number): Promise<string> {
const creditNote = await this.creditNoteModel
.query()
.findById(creditNoteId);
return `Credit-${creditNote.creditNoteNumber}`;
}
/**
* Retrieves credit note branding attributes.
* @param {number} creditNoteId - The ID of the credit note.
* @returns {Promise<CreditNotePdfTemplateAttributes>} The credit note branding attributes.
*/
public async getCreditNoteBrandingAttributes(
creditNoteId: number,
): Promise<CreditNotePdfTemplateAttributes> {
const creditNote =
await this.getCreditNoteService.getCreditNote(creditNoteId);
// Retrieve the invoice template id of not found get the default template id.
const templateId =
creditNote.pdfTemplateId ??
(
await this.pdfTemplateModel.query().findOne({
resource: 'CreditNote',
default: true,
})
)?.id;
// Retrieves the credit note branding template.
const brandingTemplate =
await this.creditNoteBrandingTemplate.getCreditNoteBrandingTemplate(
templateId,
);
return {
...brandingTemplate.attributes,
...transformCreditNoteToPdfTemplate(creditNote),
};
}
}

View File

@@ -0,0 +1,37 @@
import { Inject, Injectable } from '@nestjs/common';
import RefundCreditNoteTransformer from './RefundCreditNoteTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { IRefundCreditNotePOJO } from '../types/CreditNotes.types';
@Injectable()
export class ListCreditNoteRefunds {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
) {}
/**
* Retrieve the credit note graph.
* @param {number} creditNoteId
* @returns {Promise<IRefundCreditNotePOJO[]>}
*/
public async getCreditNoteRefunds(
creditNoteId: number,
): Promise<IRefundCreditNotePOJO[]> {
// Retrieve refund credit notes associated to the given credit note.
const refundCreditTransactions = await this.refundCreditNoteModel
.query()
.where('creditNoteId', creditNoteId)
.withGraphFetched('creditNote')
.withGraphFetched('fromAccount');
// Transforms refund credit note models to POJO objects.
return this.transformer.transform(
refundCreditTransactions,
new RefundCreditNoteTransformer(),
);
}
}

View File

@@ -0,0 +1,25 @@
import { Inject, Injectable } from '@nestjs/common';
import { ICreditNoteState } from '../types/CreditNotes.types';
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
@Injectable()
export class GetCreditNoteState {
constructor(
@Inject(PdfTemplateModel.name)
private pdfTemplateModel: typeof PdfTemplateModel,
) {}
/**
* Retrieves the create/edit initial state of the payment received.
* @return {Promise<ICreditNoteState>}
*/
public async getCreditNoteState(): Promise<ICreditNoteState> {
const defaultPdfTemplate = await this.pdfTemplateModel.query()
.findOne({ resource: 'CreditNote' })
.modify('default');
return {
defaultTemplateId: defaultPdfTemplate?.id,
};
}
}

View File

@@ -0,0 +1,68 @@
// import { Service, Inject } from 'typedi';
// import * as R from 'ramda';
// import { ICreditNotesQueryDTO } from '@/interfaces';
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
// import BaseCreditNotes from './commands/CommandCreditNoteDTOTransform.service';
// import { CreditNoteTransformer } from './queries/CreditNoteTransformer';
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
// @Service()
// export default class ListCreditNotes extends BaseCreditNotes {
// @Inject()
// private dynamicListService: DynamicListingService;
// @Inject()
// private transformer: TransformerInjectable;
// /**
// * Parses the sale invoice list filter DTO.
// * @param filterDTO
// * @returns
// */
// private parseListFilterDTO = (filterDTO) => {
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
// };
// /**
// * Retrieves the paginated and filterable credit notes list.
// * @param {number} tenantId -
// * @param {ICreditNotesQueryDTO} creditNotesQuery -
// */
// public getCreditNotesList = async (
// tenantId: number,
// creditNotesQuery: ICreditNotesQueryDTO
// ) => {
// const { CreditNote } = this.tenancy.models(tenantId);
// // Parses stringified filter roles.
// const filter = this.parseListFilterDTO(creditNotesQuery);
// // Dynamic list service.
// const dynamicFilter = await this.dynamicListService.dynamicList(
// tenantId,
// CreditNote,
// filter
// );
// const { results, pagination } = await CreditNote.query()
// .onBuild((builder) => {
// builder.withGraphFetched('entries.item');
// builder.withGraphFetched('customer');
// dynamicFilter.buildQuery()(builder);
// creditNotesQuery?.filterQuery && creditNotesQuery?.filterQuery(builder);
// })
// .pagination(filter.page - 1, filter.pageSize);
// // Transforomes the credit notes to POJO.
// const creditNotes = await this.transformer.transform(
// tenantId,
// results,
// new CreditNoteTransformer()
// );
// return {
// creditNotes,
// pagination,
// filterMeta: dynamicFilter.getResponseMeta(),
// };
// };
// }

View File

@@ -0,0 +1,37 @@
import { Inject, Injectable } from '@nestjs/common';
import { IRefundCreditNote } from '../types/CreditNotes.types';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { RefundCreditNoteTransformer } from './RefundCreditNoteTransformer';
@Injectable()
export class GetRefundCreditNoteTransaction {
/**
* @param {RefundCreditNoteTransformer} transformer
* @param {typeof RefundCreditNote} refundCreditNoteModel
*/
constructor(
private readonly transformer: RefundCreditNoteTransformer,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
) {
}
/**
* Retrieve credit note associated invoices to apply.
* @param {number} refundCreditId
* @returns {Promise<IRefundCreditNote>}
*/
public async getRefundCreditTransaction(
refundCreditId: number
): Promise<IRefundCreditNote> {
const refundCreditNote = await this.refundCreditNoteModel
.query()
.findById(refundCreditId)
.withGraphFetched('fromAccount')
.withGraphFetched('creditNote')
.throwIfNotFound();
return this.transformer.transform(refundCreditNote);
}
}

View File

@@ -0,0 +1,29 @@
import { Transformer } from "@/modules/Transformer/Transformer";
export class RefundCreditNoteTransformer extends Transformer{
/**
* Includeded attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['formttedAmount', 'formattedDate'];
};
/**
* Formatted amount.
* @returns {string}
*/
protected formttedAmount = (item) => {
return this.formatNumber(item.amount, {
currencyCode: item.currencyCode,
});
};
/**
* Formatted date.
* @returns {string}
*/
protected formattedDate = (item) => {
return this.formatDate(item.date);
};
}