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,113 @@
import { Injectable } from '@nestjs/common';
import moment from 'moment';
import { omit } from 'lodash';
import * as R from 'ramda';
import composeAsync from 'async/compose';
import { ERRORS } from '../constants';
import {
ICreditNote,
ICreditNoteEditDTO,
ICreditNoteEntryNewDTO,
ICreditNoteNewDTO,
} from '../types/CreditNotes.types';
import { ServiceError } from '@/modules/Items/ServiceError';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { BrandingTemplateDTOTransformer } from '../../PdfTemplate/BrandingTemplateDTOTransformer';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { CreditNoteAutoIncrementService } from './CreditNoteAutoIncrement.service';
import { CreditNote } from '../models/CreditNote';
@Injectable()
export class CommandCreditNoteDTOTransform {
/**
* @param {ItemsEntriesService} itemsEntriesService - The items entries service.
* @param {BranchTransactionDTOTransformer} branchDTOTransform - The branch transaction DTO transformer.
* @param {WarehouseTransactionDTOTransform} warehouseDTOTransform - The warehouse transaction DTO transformer.
* @param {BrandingTemplateDTOTransformer} brandingTemplatesTransformer - The branding template DTO transformer.
* @param {CreditNoteAutoIncrementService} creditNoteAutoIncrement - The credit note auto increment service.
*/
constructor(
private readonly itemsEntriesService: ItemsEntriesService,
private readonly branchDTOTransform: BranchTransactionDTOTransformer,
private readonly warehouseDTOTransform: WarehouseTransactionDTOTransform,
private readonly brandingTemplatesTransformer: BrandingTemplateDTOTransformer,
private readonly creditNoteAutoIncrement: CreditNoteAutoIncrementService
) {}
/**
* Transformes the credit/edit DTO to model.
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
* @param {string} customerCurrencyCode -
*/
public transformCreateEditDTOToModel = async (
creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO,
customerCurrencyCode: string,
oldCreditNote?: CreditNote,
): Promise<CreditNote> => {
// Retrieve the total amount of the given items entries.
const amount = this.itemsEntriesService.getTotalItemsEntries(
creditNoteDTO.entries,
);
const entries = R.compose(
// Associate the default index to each item entry.
assocItemEntriesDefaultIndex,
// Associate the reference type to credit note entries.
R.map((entry: ICreditNoteEntryNewDTO) => ({
...entry,
referenceType: 'CreditNote',
})),
)(creditNoteDTO.entries);
// Retreive the next credit note number.
const autoNextNumber = this.creditNoteAutoIncrement.getNextCreditNumber();
// Detarmines the credit note number.
const creditNoteNumber =
creditNoteDTO.creditNoteNumber ||
oldCreditNote?.creditNoteNumber ||
autoNextNumber;
const initialDTO = {
...omit(creditNoteDTO, ['open', 'attachments']),
creditNoteNumber,
amount,
currencyCode: customerCurrencyCode,
exchangeRate: creditNoteDTO.exchangeRate || 1,
entries,
...(creditNoteDTO.open &&
!oldCreditNote?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
refundedAmount: 0,
invoicesAmount: 0,
};
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
'CreditNote',
),
)(initialDTO);
return R.compose(
this.branchDTOTransform.transformDTO<ICreditNote>,
this.warehouseDTOTransform.transformDTO<ICreditNote>,
)(initialAsyncDTO);
};
/**
* Validate the credit note remaining amount.
* @param {ICreditNote} creditNote
* @param {number} amount
*/
public validateCreditRemainingAmount = (
creditNote: CreditNote,
amount: number,
) => {
if (creditNote.creditsRemaining < amount) {
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT);
}
};
}

View File

@@ -0,0 +1,83 @@
import { Knex } from 'knex';
import { Injectable } from '@nestjs/common';
import {
ICreditNoteCreatedPayload,
ICreditNoteCreatingPayload,
ICreditNoteNewDTO,
} from '../types/CreditNotes.types';
import { CreditNote } from '../models/CreditNote';
import { Contact } from '../../Contacts/models/Contact';
import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
@Injectable()
export class CreateCreditNoteService {
constructor(
private readonly uow: UnitOfWork,
private readonly itemsEntriesService: ItemsEntriesService,
private readonly eventPublisher: EventEmitter2,
private readonly creditNoteModel: typeof CreditNote,
private readonly contactModel: typeof Contact,
private readonly commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
) {}
/**
* Creates a new credit note.
* @param creditNoteDTO
*/
public creditCreditNote = async (
creditNoteDTO: ICreditNoteNewDTO,
trx?: Knex.Transaction,
) => {
// Triggers `onCreditNoteCreate` event.
await this.eventPublisher.emitAsync(events.creditNote.onCreate, {
creditNoteDTO,
});
// Validate customer existance.
const customer = await this.contactModel
.query()
.modify('customer')
.findById(creditNoteDTO.customerId)
.throwIfNotFound();
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(
creditNoteDTO.entries,
);
// Validate items should be sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
creditNoteDTO.entries,
);
// Transformes the given DTO to storage layer data.
const creditNoteModel =
await this.commandCreditNoteDTOTransform.transformCreateEditDTOToModel(
creditNoteDTO,
customer.currencyCode,
);
// Creates a new credit card transactions under unit-of-work envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCreditNoteCreating` event.
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
creditNoteDTO,
trx,
} as ICreditNoteCreatingPayload);
// Upsert the credit note graph.
const creditNote = await this.creditNoteModel.query(trx).upsertGraph({
...creditNoteModel,
});
// Triggers `onCreditNoteCreated` event.
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
creditNoteDTO,
creditNote,
creditNoteId: creditNote.id,
trx,
} as ICreditNoteCreatedPayload);
return creditNote;
}, trx);
};
}

View File

@@ -0,0 +1,113 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ICreditNoteRefundDTO,
IRefundCreditNoteCreatedPayload,
IRefundCreditNoteCreatingPayload,
} from '../types/CreditNotes.types';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Account } from '@/modules/Accounts/models/Account.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { events } from '@/common/events/events';
import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service';
import { CreditNote } from '../models/CreditNote';
@Injectable()
export class CreateRefundCreditNoteService {
/**
* @param {UnitOfWork} uow - The unit of work service.
* @param {EventEmitter2} eventPublisher - The event emitter service.
* @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - The command credit note DTO transform service.
* @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model.
* @param {typeof Account} accountModel - The account model.
* @param {typeof CreditNote} creditNoteModel - The credit note model.
*/
constructor(
private uow: UnitOfWork,
private eventPublisher: EventEmitter2,
private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
@Inject(RefundCreditNote.name)
private refundCreditNoteModel: typeof RefundCreditNote,
@Inject(Account.name) private accountModel: typeof Account,
@Inject(CreditNote.name) private creditNoteModel: typeof CreditNote,
) {}
/**
* Retrieve the credit note graph.
* @param {number} creditNoteId
* @param {ICreditNoteRefundDTO} newCreditNoteDTO
* @returns {Promise<IRefundCreditNote>}
*/
public async createCreditNoteRefund(
creditNoteId: number,
newCreditNoteDTO: ICreditNoteRefundDTO,
): Promise<RefundCreditNote> {
// Retrieve the credit note or throw not found service error.
const creditNote = await this.creditNoteModel
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Retrieve the withdrawal account or throw not found service error.
const fromAccount = await this.accountModel
.query()
.findById(newCreditNoteDTO.fromAccountId)
.throwIfNotFound();
// Validate the credit note remaining amount.
this.commandCreditNoteDTOTransform?.validateCreditRemainingAmount(
creditNote,
newCreditNoteDTO.amount,
);
// Validate the refund withdrawal account type.
this.commandCreditNoteDTOTransform.validateRefundWithdrawwalAccountType(
fromAccount,
);
// Creates a refund credit note transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCreditNoteRefundCreating` event.
await this.eventPublisher.emitAsync(events.creditNote.onRefundCreating, {
trx,
creditNote,
newCreditNoteDTO,
} as IRefundCreditNoteCreatingPayload);
// Stores the refund credit note graph to the storage layer.
const refundCreditNote = await this.refundCreditNoteModel
.query(trx)
.insertAndFetch({
...this.transformDTOToModel(creditNote, newCreditNoteDTO),
});
// Triggers `onCreditNoteRefundCreated` event.
await this.eventPublisher.emitAsync(events.creditNote.onRefundCreated, {
trx,
refundCreditNote,
creditNote,
} as IRefundCreditNoteCreatedPayload);
return refundCreditNote;
});
}
/**
* Transformes the refund credit note DTO to model.
* @param {number} creditNoteId
* @param {ICreditNoteRefundDTO} creditNoteDTO
* @returns {ICreditNote}
*/
private transformDTOToModel = (
creditNote: CreditNote,
creditNoteDTO: ICreditNoteRefundDTO,
): RefundCreditNote => {
return {
creditNoteId: creditNote.id,
currencyCode: creditNote.currencyCode,
...creditNoteDTO,
exchangeRate: creditNoteDTO.exchangeRate || 1,
};
};
}

View File

@@ -0,0 +1,45 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '../models/CreditNote';
@Injectable()
export class CreditNoteApplySyncCredit {
constructor(
@Inject(CreditNote.name)
private creditNoteModel: typeof CreditNote,
) {}
/**
* Increment credit note invoiced amount.
* @param {number} creditNoteId
* @param {number} invoicesAppliedAmount
* @param {Knex.Transaction} [trx]
*/
public async incrementCreditNoteInvoicedAmount(
creditNoteId: number,
invoicesAppliedAmount: number,
trx?: Knex.Transaction,
): Promise<void> {
await this.creditNoteModel
.query(trx)
.findById(creditNoteId)
.increment('invoicesAmount', invoicesAppliedAmount);
}
/**
* Decrement credit note invoiced amount.
* @param {number} creditNoteId
* @param {number} invoicesAppliedAmount
* @param {Knex.Transaction} [trx]
*/
public async decrementCreditNoteInvoicedAmount(
creditNoteId: number,
invoicesAppliedAmount: number,
trx?: Knex.Transaction,
): Promise<void> {
await this.creditNoteModel
.query(trx)
.findById(creditNoteId)
.decrement('invoicesAmount', invoicesAppliedAmount);
}
}

View File

@@ -0,0 +1,48 @@
import { Knex } from 'knex';
import { Injectable, Inject } from '@nestjs/common';
import Bluebird from 'bluebird';
import { ICreditNoteAppliedToInvoice } from '../types/CreditNotes.types';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
@Injectable()
export class CreditNoteApplySyncInvoicesCreditedAmount {
constructor(
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
) {}
/**
* Increment invoices credited amount.
* @param {ICreditNoteAppliedToInvoice[]} creditNoteAppliedInvoices -
* @param {Knex.Transaction} trx -
*/
public incrementInvoicesCreditedAmount = async (
creditNoteAppliedInvoices: ICreditNoteAppliedToInvoice[],
trx?: Knex.Transaction
) => {
await Bluebird.each(
creditNoteAppliedInvoices,
(creditNoteAppliedInvoice: ICreditNoteAppliedToInvoice) => {
return this.saleInvoiceModel.query(trx)
.where('id', creditNoteAppliedInvoice.invoiceId)
.increment('creditedAmount', creditNoteAppliedInvoice.amount);
}
);
};
/**
*
* @param {number} invoicesIds
* @param {number} amount -
* @param {Knex.Transaction} knex -
*/
public decrementInvoiceCreditedAmount = async (
invoiceId: number,
amount: number,
trx?: Knex.Transaction
) => {
await this.saleInvoiceModel.query(trx)
.findById(invoiceId)
.decrement('creditedAmount', amount);
};
}

View File

@@ -0,0 +1,131 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { sumBy } from 'lodash';
import {
ICreditNoteAppliedToInvoice,
ICreditNoteAppliedToInvoiceModel,
IApplyCreditToInvoicesDTO,
IApplyCreditToInvoicesCreatedPayload,
ICreditNote,
} from '../types/CreditNotes.types';
import { ERRORS } from '../constants';
import { PaymentReceivedValidators } from '@/modules/PaymentReceived/commands/PaymentReceivedValidators.service';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
import { events } from '@/common/events/events';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class CreditNoteApplyToInvoices {
/**
* @param {PaymentReceivedValidators} paymentReceiveValidators - The payment received validators service.
* @param {UnitOfWork} uow - The unit of work service.
* @param {EventEmitter2} eventPublisher - The event emitter service.
* @param {typeof CreditNoteAppliedInvoice} creditNoteAppliedInvoiceModel - The credit note applied invoice model.
*/
constructor(
private readonly paymentReceiveValidators: PaymentReceivedValidators,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(CreditNoteAppliedInvoice.name)
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
) {}
/**
* Apply credit note to the given invoices.
* @param {number} creditNoteId
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
*/
public async applyCreditNoteToInvoices(
creditNoteId: number,
applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO,
): Promise<ICreditNoteAppliedToInvoice[]> {
// Saves the credit note or throw not found service error.
const creditNote = await this.getCreditNoteOrThrowError(creditNoteId);
// Retrieve the applied invoices that associated to the credit note customer.
const appliedInvoicesEntries =
await this.paymentReceiveValidators.validateInvoicesIDsExistance(
creditNote.customerId,
applyCreditToInvoicesDTO.entries,
);
// Transformes apply DTO to model.
const creditNoteAppliedModel = this.transformApplyDTOToModel(
applyCreditToInvoicesDTO,
creditNote,
);
// Validate invoices has remaining amount to apply.
this.validateInvoicesRemainingAmount(
appliedInvoicesEntries,
creditNoteAppliedModel.amount,
);
// Validate the credit note remaining amount.
this.validateCreditRemainingAmount(
creditNote,
creditNoteAppliedModel.amount,
);
// Creates credit note apply to invoice transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Saves the credit note apply to invoice graph to the storage layer.
const creditNoteAppliedInvoices = await this.creditNoteAppliedInvoiceModel
.query()
.insertGraph(creditNoteAppliedModel.entries);
// Triggers `onCreditNoteApplyToInvoiceCreated` event.
await this.eventPublisher.emitAsync(
events.creditNote.onApplyToInvoicesCreated,
{
creditNote,
creditNoteAppliedInvoices,
trx,
} as IApplyCreditToInvoicesCreatedPayload,
);
return creditNoteAppliedInvoices;
});
}
/**
* Transformes apply DTO to model.
* @param {IApplyCreditToInvoicesDTO} applyDTO
* @param {ICreditNote} creditNote
* @returns
*/
private transformApplyDTOToModel = (
applyDTO: IApplyCreditToInvoicesDTO,
creditNote: ICreditNote,
): ICreditNoteAppliedToInvoiceModel => {
const entries = applyDTO.entries.map((entry) => ({
invoiceId: entry.invoiceId,
amount: entry.amount,
creditNoteId: creditNote.id,
}));
return {
amount: sumBy(entries, 'amount'),
entries,
};
};
/**
* Validate the invoice remaining amount.
* @param {ISaleInvoice[]} invoices
* @param {number} amount
*/
private validateInvoicesRemainingAmount = (
invoices: SaleInvoice[],
amount: number,
) => {
const invalidInvoices = invoices.filter(
(invoice) => invoice.dueAmount < amount,
);
if (invalidInvoices.length > 0) {
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
}
};
}

View File

@@ -0,0 +1,28 @@
import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service';
import { Injectable } from '@nestjs/common';
@Injectable()
export class CreditNoteAutoIncrementService {
constructor(
private readonly autoIncrementOrdersService: AutoIncrementOrdersService,
) {}
/**
* Retrieve the next unique credit number.
* @return {string}
*/
public getNextCreditNumber(): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
'credit_note',
);
}
/**
* Increment the credit note serial next number.
*/
public incrementSerialNumber() {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
'credit_note',
);
}
}

View File

@@ -0,0 +1,297 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import * as R from 'ramda';
// import {
// AccountNormal,
// IItemEntry,
// ILedgerEntry,
// ICreditNote,
// ILedger,
// ICreditNoteGLCommonEntry,
// } from '@/interfaces';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { SaleReceipt } from '@/models';
// @Service()
// export default class CreditNoteGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// /**
// * Retrieves the credit note GL.
// * @param {ICreditNote} creditNote
// * @param {number} receivableAccount
// * @returns {Ledger}
// */
// private getCreditNoteGLedger = (
// creditNote: ICreditNote,
// receivableAccount: number,
// discountAccount: number,
// adjustmentAccount: number
// ): Ledger => {
// const ledgerEntries = this.getCreditNoteGLEntries(
// creditNote,
// receivableAccount,
// discountAccount,
// adjustmentAccount
// );
// return new Ledger(ledgerEntries);
// };
// /**
// * Saves credit note GL entries.
// * @param {number} tenantId -
// * @param {ICreditNote} creditNote - Credit note model.
// * @param {number} payableAccount - Payable account id.
// * @param {Knex.Transaction} trx
// */
// public saveCreditNoteGLEntries = async (
// tenantId: number,
// creditNote: ICreditNote,
// payableAccount: number,
// discountAccount: number,
// adjustmentAccount: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const ledger = this.getCreditNoteGLedger(
// creditNote,
// payableAccount,
// discountAccount,
// adjustmentAccount
// );
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
// /**
// * Reverts the credit note associated GL entries.
// * @param {number} tenantId
// * @param {number} vendorCreditId
// * @param {Knex.Transaction} trx
// */
// public revertVendorCreditGLEntries = async (
// tenantId: number,
// creditNoteId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// await this.ledgerStorage.deleteByReference(
// tenantId,
// creditNoteId,
// 'CreditNote',
// trx
// );
// };
// /**
// * Writes vendor credit associated GL entries.
// * @param {number} tenantId - Tenant id.
// * @param {number} creditNoteId - Credit note id.
// * @param {Knex.Transaction} trx - Knex transactions.
// */
// public createVendorCreditGLEntries = async (
// tenantId: number,
// creditNoteId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { CreditNote } = this.tenancy.models(tenantId);
// const { accountRepository } = this.tenancy.repositories(tenantId);
// // Retrieve the credit note with associated entries and items.
// const creditNoteWithItems = await CreditNote.query(trx)
// .findById(creditNoteId)
// .withGraphFetched('entries.item');
// // Retreive the the `accounts receivable` account based on the given currency.
// const ARAccount = await accountRepository.findOrCreateAccountReceivable(
// creditNoteWithItems.currencyCode
// );
// const discountAccount = await accountRepository.findOrCreateDiscountAccount(
// {}
// );
// const adjustmentAccount =
// await accountRepository.findOrCreateOtherChargesAccount({});
// // Saves the credit note GL entries.
// await this.saveCreditNoteGLEntries(
// tenantId,
// creditNoteWithItems,
// ARAccount.id,
// discountAccount.id,
// adjustmentAccount.id,
// trx
// );
// };
// /**
// * Edits vendor credit associated GL entries.
// * @param {number} tenantId
// * @param {number} creditNoteId
// * @param {Knex.Transaction} trx
// */
// public editVendorCreditGLEntries = async (
// tenantId: number,
// creditNoteId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Reverts vendor credit GL entries.
// await this.revertVendorCreditGLEntries(tenantId, creditNoteId, trx);
// // Creates vendor credit Gl entries.
// await this.createVendorCreditGLEntries(tenantId, creditNoteId, trx);
// };
// /**
// * Retrieve the credit note common entry.
// * @param {ICreditNote} creditNote -
// * @returns {ICreditNoteGLCommonEntry}
// */
// private getCreditNoteCommonEntry = (
// creditNote: ICreditNote
// ): ICreditNoteGLCommonEntry => {
// return {
// date: creditNote.creditNoteDate,
// userId: creditNote.userId,
// currencyCode: creditNote.currencyCode,
// exchangeRate: creditNote.exchangeRate,
// transactionType: 'CreditNote',
// transactionId: creditNote.id,
// transactionNumber: creditNote.creditNoteNumber,
// referenceNumber: creditNote.referenceNo,
// createdAt: creditNote.createdAt,
// indexGroup: 10,
// credit: 0,
// debit: 0,
// branchId: creditNote.branchId,
// };
// };
// /**
// * Retrieves the creidt note A/R entry.
// * @param {ICreditNote} creditNote -
// * @param {number} ARAccountId -
// * @returns {ILedgerEntry}
// */
// private getCreditNoteAREntry = (
// creditNote: ICreditNote,
// ARAccountId: number
// ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// return {
// ...commonEntry,
// credit: creditNote.totalLocal,
// accountId: ARAccountId,
// contactId: creditNote.customerId,
// index: 1,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieve the credit note item entry.
// * @param {ICreditNote} creditNote
// * @param {IItemEntry} entry
// * @param {number} index
// * @returns {ILedgerEntry}
// */
// private getCreditNoteItemEntry = R.curry(
// (
// creditNote: ICreditNote,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// const totalLocal = entry.totalExcludingTax * creditNote.exchangeRate;
// return {
// ...commonEntry,
// debit: totalLocal,
// accountId: entry.sellAccountId || entry.item.sellAccountId,
// note: entry.description,
// index: index + 2,
// itemId: entry.itemId,
// itemQuantity: entry.quantity,
// accountNormal: AccountNormal.CREDIT,
// };
// }
// );
// /**
// * Retrieves the credit note discount entry.
// * @param {ICreditNote} creditNote
// * @param {number} discountAccountId
// * @returns {ILedgerEntry}
// */
// private getDiscountEntry = (
// creditNote: ICreditNote,
// discountAccountId: number
// ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// return {
// ...commonEntry,
// credit: creditNote.discountAmountLocal,
// accountId: discountAccountId,
// index: 1,
// accountNormal: AccountNormal.CREDIT,
// };
// };
// /**
// * Retrieves the credit note adjustment entry.
// * @param {ICreditNote} creditNote
// * @param {number} adjustmentAccountId
// * @returns {ILedgerEntry}
// */
// private getAdjustmentEntry = (
// creditNote: ICreditNote,
// adjustmentAccountId: number
// ): ILedgerEntry => {
// const commonEntry = this.getCreditNoteCommonEntry(creditNote);
// const adjustmentAmount = Math.abs(creditNote.adjustmentLocal);
// return {
// ...commonEntry,
// credit: creditNote.adjustmentLocal < 0 ? adjustmentAmount : 0,
// debit: creditNote.adjustmentLocal > 0 ? adjustmentAmount : 0,
// accountId: adjustmentAccountId,
// accountNormal: AccountNormal.CREDIT,
// index: 1,
// };
// };
// /**
// * Retrieve the credit note GL entries.
// * @param {ICreditNote} creditNote - Credit note.
// * @param {IAccount} receivableAccount - Receviable account.
// * @returns {ILedgerEntry[]} - Ledger entries.
// */
// public getCreditNoteGLEntries = (
// creditNote: ICreditNote,
// ARAccountId: number,
// discountAccountId: number,
// adjustmentAccountId: number
// ): ILedgerEntry[] => {
// const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId);
// const getItemEntry = this.getCreditNoteItemEntry(creditNote);
// const itemsEntries = creditNote.entries.map(getItemEntry);
// const discountEntry = this.getDiscountEntry(creditNote, discountAccountId);
// const adjustmentEntry = this.getAdjustmentEntry(
// creditNote,
// adjustmentAccountId
// );
// return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries];
// };
// }

View File

@@ -0,0 +1,68 @@
import { Transformer } from "@/modules/Transformer/Transformer";
export class CreditNoteWithInvoicesToApplyTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedInvoiceDate',
'formattedDueDate',
'formattedAmount',
'formattedDueAmount',
'formattedPaymentAmount',
];
};
/**
* Retrieve formatted invoice date.
* @param {ISaleInvoice} invoice
* @returns {String}
*/
protected formattedInvoiceDate = (invoice): string => {
return this.formatDate(invoice.invoiceDate);
};
/**
* Retrieve formatted due date.
* @param {ISaleInvoice} invoice
* @returns {string}
*/
protected formattedDueDate = (invoice): string => {
return this.formatDate(invoice.dueDate);
};
/**
* Retrieve formatted invoice amount.
* @param {ISaleInvoice} invoice
* @returns {string}
*/
protected formattedAmount = (invoice): string => {
return this.formatNumber(invoice.balance, {
currencyCode: invoice.currencyCode,
});
};
/**
* Retrieve formatted invoice due amount.
* @param {ISaleInvoice} invoice
* @returns {string}
*/
protected formattedDueAmount = (invoice): string => {
return this.formatNumber(invoice.dueAmount, {
currencyCode: invoice.currencyCode,
});
};
/**
* Retrieve formatted payment amount.
* @param {ISaleInvoice} invoice
* @returns {string}
*/
protected formattedPaymentAmount = (invoice): string => {
return this.formatNumber(invoice.paymentAmount, {
currencyCode: invoice.currencyCode,
});
};
}

View File

@@ -0,0 +1,35 @@
// import { Inject, Service } from 'typedi';
// import { ICreditNotesQueryDTO } from '@/interfaces';
// import { Exportable } from '@/services/Export/Exportable';
// import ListCreditNotes from '../ListCreditNotes';
// @Service()
// export class CreditNotesExportable extends Exportable {
// @Inject()
// private getCreditNotes: ListCreditNotes;
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId -
// * @param {IVendorCreditsQueryDTO} query -
// * @returns {}
// */
// public exportable(tenantId: number, query: ICreditNotesQueryDTO) {
// const filterQuery = (query) => {
// query.withGraphFetched('branch');
// query.withGraphFetched('warehouse');
// };
// const parsedQuery = {
// sortOrder: 'desc',
// columnSortBy: 'created_at',
// ...query,
// page: 1,
// pageSize: 12000,
// filterQuery,
// } as ICreditNotesQueryDTO;
// return this.getCreditNotes
// .getCreditNotesList(tenantId, parsedQuery)
// .then((output) => output.creditNotes);
// }
// }

View File

@@ -0,0 +1,44 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { ICreditNoteNewDTO } from '@/interfaces';
// import { Importable } from '../Import/Importable';
// import CreateCreditNote from './commands/CreateCreditNote.service';
// @Service()
// export class CreditNotesImportable extends Importable {
// @Inject()
// private createCreditNoteImportable: CreateCreditNote;
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: ICreditNoteNewDTO,
// trx?: Knex.Transaction
// ) {
// return this.createCreditNoteImportable.newCreditNote(
// tenantId,
// createAccountDTO,
// trx
// );
// }
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return [];
// }
// }

View File

@@ -0,0 +1,90 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { ICreditNote } from '@/interfaces';
// import InventoryService from '@/services/Inventory/Inventory';
// import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
// @Service()
// export default class CreditNoteInventoryTransactions {
// @Inject()
// inventoryService: InventoryService;
// @Inject()
// itemsEntriesService: ItemsEntriesService;
// /**
// * Creates credit note inventory transactions.
// * @param {number} tenantId
// * @param {ICreditNote} creditNote
// */
// public createInventoryTransactions = async (
// tenantId: number,
// creditNote: ICreditNote,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Loads the inventory items entries of the given sale invoice.
// const inventoryEntries =
// await this.itemsEntriesService.filterInventoryEntries(
// tenantId,
// creditNote.entries
// );
// const transaction = {
// transactionId: creditNote.id,
// transactionType: 'CreditNote',
// transactionNumber: creditNote.creditNoteNumber,
// exchangeRate: creditNote.exchangeRate,
// date: creditNote.creditNoteDate,
// direction: 'IN',
// entries: inventoryEntries,
// createdAt: creditNote.createdAt,
// warehouseId: creditNote.warehouseId,
// };
// // Writes inventory tranactions.
// await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
// tenantId,
// transaction,
// false,
// trx
// );
// };
// /**
// * Edits vendor credit associated inventory transactions.
// * @param {number} tenantId
// * @param {number} creditNoteId
// * @param {ICreditNote} creditNote
// * @param {Knex.Transactions} trx
// */
// public editInventoryTransactions = async (
// tenantId: number,
// creditNoteId: number,
// creditNote: ICreditNote,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Deletes inventory transactions.
// await this.deleteInventoryTransactions(tenantId, creditNoteId, trx);
// // Re-write inventory transactions.
// await this.createInventoryTransactions(tenantId, creditNote, trx);
// };
// /**
// * Deletes credit note associated inventory transactions.
// * @param {number} tenantId - Tenant id.
// * @param {number} creditNoteId - Credit note id.
// * @param {Knex.Transaction} trx -
// */
// public deleteInventoryTransactions = async (
// tenantId: number,
// creditNoteId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Deletes the inventory transactions by the given reference id and type.
// await this.inventoryService.deleteInventoryTransactions(
// tenantId,
// creditNoteId,
// 'CreditNote',
// trx
// );
// };
// }

View File

@@ -0,0 +1,124 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ICreditNoteDeletedPayload,
ICreditNoteDeletingPayload,
} from '../types/CreditNotes.types';
import { ERRORS } from '../constants';
import { CreditNote } from '../models/CreditNote';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
import {
RefundCreditNote as RefundCreditNoteModel,
} from '../models/RefundCreditNote';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
@Injectable()
export class DeleteCreditNoteService {
/**
* @param {UnitOfWork} uow - Unit of work.
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {typeof CreditNote} creditNoteModel - Credit note model.
* @param {typeof ItemEntry} itemEntryModel - Item entry model.
* @param {typeof CreditNoteAppliedInvoice} creditNoteAppliedInvoiceModel - Credit note applied invoice model.
* @param {typeof RefundCreditNote} refundCreditNoteModel - Refund credit note model.
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
@Inject(ItemEntry.name)
private readonly itemEntryModel: typeof ItemEntry,
@Inject(CreditNoteAppliedInvoice.name)
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(RefundCreditNoteModel.name)
private readonly refundCreditNoteModel: typeof RefundCreditNoteModel,
) {}
/**
* Deletes the given credit note transactions.
* @param {number} creditNoteId
* @returns {Promise<void>}
*/
public async deleteCreditNote(creditNoteId: number): Promise<void> {
// Retrieve the credit note or throw not found service error.
const oldCreditNote = await this.creditNoteModel
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Validate credit note has no refund transactions.
await this.validateCreditNoteHasNoRefundTransactions(creditNoteId);
// Validate credit note has no applied invoices transactions.
await this.validateCreditNoteHasNoApplyInvoiceTransactions(creditNoteId);
// Deletes the credit note transactions under unit-of-work transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCreditNoteDeleting` event.
await this.eventPublisher.emitAsync(events.creditNote.onDeleting, {
trx,
oldCreditNote,
} as ICreditNoteDeletingPayload);
// Deletes the associated credit note entries.
await this.itemEntryModel
.query(trx)
.where('reference_id', creditNoteId)
.where('reference_type', 'CreditNote')
.delete();
// Deletes the credit note transaction.
await this.creditNoteModel.query(trx).findById(creditNoteId).delete();
// Triggers `onCreditNoteDeleted` event.
await this.eventPublisher.emitAsync(events.creditNote.onDeleted, {
oldCreditNote,
creditNoteId,
trx,
} as ICreditNoteDeletedPayload);
});
}
/**
* Validates credit note has no associated refund transactions.
* @param {number} creditNoteId
* @returns {Promise<void>}
*/
private async validateCreditNoteHasNoRefundTransactions(
creditNoteId: number,
): Promise<void> {
const refundTransactions = await this.refundCreditNoteModel
.query()
.where('creditNoteId', creditNoteId);
if (refundTransactions.length > 0) {
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS);
}
}
/**
* Validate credit note has no associated applied invoices transactions.
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<void>}
*/
private async validateCreditNoteHasNoApplyInvoiceTransactions(
creditNoteId: number,
): Promise<void> {
const appliedTransactions = await this.creditNoteAppliedInvoiceModel
.query()
.where('creditNoteId', creditNoteId);
if (appliedTransactions.length > 0) {
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_APPLIED_INVOICES);
}
}
}

View File

@@ -0,0 +1,63 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { IApplyCreditToInvoicesDeletedPayload } from '../types/CreditNotes.types';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { ServiceError } from '@/modules/Items/ServiceError';
import { CreditNote } from '../models/CreditNote';
import { ERRORS } from '../constants';
@Injectable()
export default class DeleteCreditNoteApplyToInvoices {
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
) {}
/**
* Apply credit note to the given invoices.
* @param {number} creditNoteId
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
*/
public deleteApplyCreditNoteToInvoices = async (
applyCreditToInvoicesId: number,
): Promise<void> => {
const creditNoteAppliedToInvoice = await this.creditNoteAppliedInvoiceModel
.query()
.findById(applyCreditToInvoicesId);
if (!creditNoteAppliedToInvoice) {
throw new ServiceError(ERRORS.CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND);
}
// Retrieve the credit note or throw not found service error.
const creditNote = await this.creditNoteModel
.query()
.findById(creditNoteAppliedToInvoice.creditNoteId)
.throwIfNotFound();
// Creates credit note apply to invoice transaction.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Delete credit note applied to invoices.
await this.creditNoteAppliedInvoiceModel
.query(trx)
.findById(applyCreditToInvoicesId)
.delete();
// Triggers `onCreditNoteApplyToInvoiceDeleted` event.
await this.eventPublisher.emitAsync(
events.creditNote.onApplyToInvoicesDeleted,
{
creditNote,
creditNoteAppliedToInvoice,
trx,
} as IApplyCreditToInvoicesDeletedPayload,
);
});
};
}

View File

@@ -0,0 +1,26 @@
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '../models/CreditNote';
import { ERRORS } from '../constants';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class DeleteCustomerLinkedCreditNoteService {
constructor(
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
) {}
/**
* Validate the given customer has no linked credit note transactions.
* @param {number} customerId - The customer identifier.
*/
public async validateCustomerHasNoCreditTransaction(customerId: number) {
const associatedCredits = await this.creditNoteModel
.query()
.where('customerId', customerId);
if (associatedCredits.length > 0) {
throw new ServiceError(ERRORS.CUSTOMER_HAS_LINKED_CREDIT_NOTES);
}
}
}

View File

@@ -0,0 +1,73 @@
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Knex } from 'knex';
import {
IRefundCreditNoteDeletedPayload,
IRefundCreditNoteDeletingPayload,
IRefundVendorCreditDeletedPayload,
} from '../types/CreditNotes.types';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
@Injectable()
export default class DeleteRefundCreditNote {
/**
* @param {UnitOfWork} uow
* @param {EventEmitter2} eventPublisher
* @param {typeof RefundCreditNoteModel} refundCreditNoteModel
*/
constructor(
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
) {}
/**
* Retrieve the credit note graph.
* @param {number} refundCreditId
* @returns
*/
public deleteCreditNoteRefund = async (refundCreditId: number) => {
// Retrieve the old credit note or throw not found service error.
const oldRefundCredit = await this.refundCreditNoteModel
.query()
.findById(refundCreditId)
.throwIfNotFound();
// Triggers `onCreditNoteRefundDeleted` event.
await this.eventPublisher.emitAsync(events.creditNote.onRefundDelete, {
refundCreditId,
oldRefundCredit,
} as IRefundCreditNoteDeletedPayload);
// Deletes refund credit note transactions with associated entries.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
const eventPayload = {
trx,
refundCreditId,
oldRefundCredit,
} as IRefundCreditNoteDeletedPayload | IRefundCreditNoteDeletingPayload;
// Triggers `onCreditNoteRefundDeleting` event.
await this.eventPublisher.emitAsync(
events.creditNote.onRefundDeleting,
eventPayload,
);
// Deletes the refund credit note graph from the storage.
await this.refundCreditNoteModel
.query(trx)
.findById(refundCreditId)
.delete();
// Triggers `onCreditNoteRefundDeleted` event.
await this.eventPublisher.emitAsync(
events.creditNote.onRefundDeleted,
eventPayload as IRefundVendorCreditDeletedPayload,
);
});
};
}

View File

@@ -0,0 +1,101 @@
import { Inject, Injectable } from '@nestjs/common';
import {
ICreditNoteEditDTO,
ICreditNoteEditedPayload,
ICreditNoteEditingPayload,
} from '../types/CreditNotes.types';
import { Knex } from 'knex';
import { CreditNote } from '../models/CreditNote';
import { Contact } from '../../Contacts/models/Contact';
import { ItemsEntriesService } from '../../Items/ItemsEntries.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service';
@Injectable()
export class EditCreditNoteService {
/**
* @param {typeof CreditNote} creditNoteModel - The credit note model.
* @param {typeof Contact} contactModel - The contact model.
* @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - The command credit note DTO transform service.
* @param {ItemsEntriesService} itemsEntriesService - The items entries service.
* @param {EventEmitter2} eventPublisher - The event publisher.
* @param {UnitOfWork} uow - The unit of work.
*/
constructor(
@Inject(CreditNote.name) private creditNoteModel: typeof CreditNote,
@Inject(Contact.name) private contactModel: typeof Contact,
private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
private itemsEntriesService: ItemsEntriesService,
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
) {}
/**
* Edits the given credit note.
* @param {ICreditNoteEditDTO} creditNoteEditDTO -
*/
public async editCreditNote(
creditNoteId: number,
creditNoteEditDTO: ICreditNoteEditDTO,
) {
// Retrieve the sale invoice or throw not found service error.
const oldCreditNote = await this.creditNoteModel
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Validate customer existance.
const customer = await this.contactModel
.query()
.findById(creditNoteEditDTO.customerId);
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(
creditNoteEditDTO.entries,
);
// Validate non-sellable entries items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
creditNoteEditDTO.entries,
);
// Validate the items entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance(
creditNoteId,
'CreditNote',
creditNoteEditDTO.entries,
);
// Transformes the given DTO to storage layer data.
const creditNoteModel =
await this.commandCreditNoteDTOTransform.transformCreateEditDTOToModel(
creditNoteEditDTO,
customer.currencyCode,
oldCreditNote,
);
// Sales the credit note transactions with associated entries.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onCreditNoteEditing` event.
await this.eventPublisher.emitAsync(events.creditNote.onEditing, {
creditNoteEditDTO,
oldCreditNote,
trx,
} as ICreditNoteEditingPayload);
// Saves the credit note graph to the storage.
const creditNote = await this.creditNoteModel.query(trx).upsertGraph({
id: creditNoteId,
...creditNoteModel,
});
// Triggers `onCreditNoteEdited` event.
await this.eventPublisher.emitAsync(events.creditNote.onEdited, {
trx,
oldCreditNote,
creditNoteId,
creditNote,
creditNoteEditDTO,
} as ICreditNoteEditedPayload);
return creditNote;
});
}
}

View File

@@ -0,0 +1,88 @@
import {
ICreditNoteOpenedPayload,
ICreditNoteOpeningPayload,
} from '../types/CreditNotes.types';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { CreditNote } from '../models/CreditNote';
import { events } from '@/common/events/events';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class OpenCreditNoteService {
/**
* @param {EventEmitter2} eventPublisher - The event publisher.
* @param {UnitOfWork} uow - The unit of work.
* @param {typeof CreditNote} creditNoteModel - The credit note model.
*/
constructor(
private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
) {}
/**
* Opens the given credit note.
* @param {number} creditNoteId -
* @returns {Promise<CreditNote>}
*/
public openCreditNote = async (creditNoteId: number): Promise<CreditNote> => {
// Retrieve the sale invoice or throw not found service error.
const oldCreditNote = await this.creditNoteModel
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Throw service error if the credit note is already open.
this.throwErrorIfAlreadyOpen(oldCreditNote);
// Triggers `onCreditNoteOpen` event.
this.eventPublisher.emitAsync(events.creditNote.onOpen, {
creditNoteId,
oldCreditNote,
});
// Sales the credit note transactions with associated entries.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
const eventPayload = {
creditNoteId,
oldCreditNote,
trx,
} as ICreditNoteOpeningPayload;
// Triggers `onCreditNoteOpening` event.
await this.eventPublisher.emitAsync(
events.creditNote.onOpening,
eventPayload,
);
// Saves the credit note graph to the storage.
const creditNote = await this.creditNoteModel
.query(trx)
.findById(creditNoteId)
.update({
openedAt: new Date(),
});
// Triggers `onCreditNoteOpened` event.
await this.eventPublisher.emitAsync(events.creditNote.onOpened, {
...eventPayload,
creditNote,
} as ICreditNoteOpenedPayload);
return creditNote;
});
};
/**
*
* @param creditNote
*/
public throwErrorIfAlreadyOpen = (creditNote) => {
if (creditNote.openedAt) {
throw new ServiceError(ERRORS.CREDIT_NOTE_ALREADY_OPENED);
}
};
}

View File

@@ -0,0 +1,45 @@
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { ServiceError } from '@/modules/Items/ServiceError';
import { Account } from '@/modules/Accounts/models/Account.model';
@Injectable()
export class RefundCreditNoteService {
/**
* @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model.
*/
constructor(
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
) {}
/**
* Retrieve the credit note graph.
* @param {number} refundCreditId
* @returns {Promise<RefundCreditNote>}
*/
public getCreditNoteRefundOrThrowError = async (
refundCreditId: number,
): Promise<RefundCreditNote> => {
const refundCreditNote = await this.refundCreditNoteModel
.query()
.findById(refundCreditId);
if (!refundCreditNote) {
throw new ServiceError(ERRORS.REFUND_CREDIT_NOTE_NOT_FOUND);
}
return refundCreditNote;
};
/**
* Validate the refund account type.
* @param {Account} account
*/
public validateRefundWithdrawwalAccountType = (account: Account): void => {
const supportedTypes = ['bank', 'cash', 'fixed-asset'];
if (supportedTypes.indexOf(account.accountType) === -1) {
throw new ServiceError(ERRORS.ACCOUNT_INVALID_TYPE);
}
};
}

View File

@@ -0,0 +1,160 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { AccountNormal, ILedgerEntry, IRefundCreditNote } from '@/interfaces';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import Ledger from '@/services/Accounting/Ledger';
// @Service()
// export default class RefundCreditNoteGLEntries {
// @Inject()
// ledgerStorage: LedgerStorageService;
// @Inject()
// tenancy: HasTenancyService;
// /**
// * Retrieves the refund credit common GL entry.
// * @param {IRefundCreditNote} refundCreditNote
// * @returns
// */
// private getRefundCreditCommonGLEntry = (
// refundCreditNote: IRefundCreditNote
// ) => {
// return {
// currencyCode: refundCreditNote.currencyCode,
// exchangeRate: refundCreditNote.exchangeRate,
// transactionType: 'RefundCreditNote',
// transactionId: refundCreditNote.id,
// date: refundCreditNote.date,
// userId: refundCreditNote.userId,
// referenceNumber: refundCreditNote.referenceNo,
// createdAt: refundCreditNote.createdAt,
// indexGroup: 10,
// credit: 0,
// debit: 0,
// note: refundCreditNote.description,
// branchId: refundCreditNote.branchId,
// };
// };
// /**
// * Retrieves the refudn credit receivable GL entry.
// * @param {IRefundCreditNote} refundCreditNote
// * @param {number} ARAccountId
// * @returns {ILedgerEntry}
// */
// private getRefundCreditGLReceivableEntry = (
// refundCreditNote: IRefundCreditNote,
// ARAccountId: number
// ): ILedgerEntry => {
// const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);
// return {
// ...commonEntry,
// debit: refundCreditNote.amount,
// accountId: ARAccountId,
// contactId: refundCreditNote.creditNote.customerId,
// index: 1,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieves the refund credit withdrawal GL entry.
// * @param {number} refundCreditNote
// * @returns {ILedgerEntry}
// */
// private getRefundCreditGLWithdrawalEntry = (
// refundCreditNote: IRefundCreditNote
// ): ILedgerEntry => {
// const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);
// return {
// ...commonEntry,
// credit: refundCreditNote.amount,
// accountId: refundCreditNote.fromAccountId,
// index: 2,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieve the refund credit note GL entries.
// * @param {IRefundCreditNote} refundCreditNote
// * @param {number} receivableAccount
// * @returns {ILedgerEntry[]}
// */
// public getRefundCreditGLEntries(
// refundCreditNote: IRefundCreditNote,
// ARAccountId: number
// ): ILedgerEntry[] {
// const receivableEntry = this.getRefundCreditGLReceivableEntry(
// refundCreditNote,
// ARAccountId
// );
// const withdrawalEntry =
// this.getRefundCreditGLWithdrawalEntry(refundCreditNote);
// return [receivableEntry, withdrawalEntry];
// }
// /**
// * Creates refund credit GL entries.
// * @param {number} tenantId
// * @param {IRefundCreditNote} refundCreditNote
// * @param {Knex.Transaction} trx
// */
// public createRefundCreditGLEntries = async (
// tenantId: number,
// refundCreditNoteId: number,
// trx?: Knex.Transaction
// ) => {
// const { Account, RefundCreditNote } = this.tenancy.models(tenantId);
// // Retrieve the refund with associated credit note.
// const refundCreditNote = await RefundCreditNote.query(trx)
// .findById(refundCreditNoteId)
// .withGraphFetched('creditNote');
// // Receivable account A/R.
// const receivableAccount = await Account.query().findOne(
// 'slug',
// 'accounts-receivable'
// );
// // Retrieve refund credit GL entries.
// const refundGLEntries = this.getRefundCreditGLEntries(
// refundCreditNote,
// receivableAccount.id
// );
// const ledger = new Ledger(refundGLEntries);
// // Saves refund ledger entries.
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
// /**
// * Reverts refund credit note GL entries.
// * @param {number} tenantId
// * @param {number} refundCreditNoteId
// * @param {number} receivableAccount
// * @param {Knex.Transaction} trx
// */
// public revertRefundCreditGLEntries = async (
// tenantId: number,
// refundCreditNoteId: number,
// trx?: Knex.Transaction
// ) => {
// await this.ledgerStorage.deleteByReference(
// tenantId,
// refundCreditNoteId,
// 'RefundCreditNote',
// trx
// );
// };
// }

View File

@@ -0,0 +1,48 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '../models/CreditNote';
@Injectable()
export class RefundSyncCreditNoteBalance {
/**
* @param {typeof CreditNote} creditNoteModel - The credit note model.
*/
constructor(
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
) {}
/**
* Increments the refund amount of the credit note.
* @param {number} creditNoteId - The credit note ID.
* @param {number} amount - The amount to increment.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public incrementCreditNoteRefundAmount = async (
creditNoteId: number,
amount: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.creditNoteModel
.query(trx)
.findById(creditNoteId)
.increment('refunded_amount', amount);
};
/**
* Decrements the refund amount of the credit note.
* @param {number} creditNoteId - The credit note ID.
* @param {number} amount - The amount to decrement.
* @param {Knex.Transaction} trx - The knex transaction.
*/
public decrementCreditNoteRefundAmount = async (
creditNoteId: number,
amount: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.creditNoteModel
.query(trx)
.findById(creditNoteId)
.decrement('refunded_amount', amount);
};
}