mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { omit } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as composeAsync from 'async/compose';
|
||||
import * as R from 'ramda';
|
||||
import { ERRORS } from '../constants';
|
||||
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';
|
||||
import {
|
||||
CreateCreditNoteDto,
|
||||
CreditNoteEntryDto,
|
||||
EditCreditNoteDto,
|
||||
} from '../dtos/CreditNote.dto';
|
||||
|
||||
@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,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Transforms the credit/edit DTO to model.
|
||||
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
|
||||
* @param {string} customerCurrencyCode -
|
||||
*/
|
||||
public transformCreateEditDTOToModel = async (
|
||||
creditNoteDTO: CreateCreditNoteDto | EditCreditNoteDto,
|
||||
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: CreditNoteEntryDto) => ({
|
||||
...entry,
|
||||
referenceType: 'CreditNote',
|
||||
})),
|
||||
)(creditNoteDTO.entries);
|
||||
|
||||
// Retrieves the next credit note number.
|
||||
const autoNextNumber = this.creditNoteAutoIncrement.getNextCreditNumber();
|
||||
|
||||
// Determines 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 asyncDto = (await composeAsync(
|
||||
this.branchDTOTransform.transformDTO<CreditNote>,
|
||||
this.warehouseDTOTransform.transformDTO<CreditNote>,
|
||||
|
||||
// Assigns the default branding template id to the invoice DTO.
|
||||
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
|
||||
'CreditNote',
|
||||
),
|
||||
)(initialDTO)) as CreditNote;
|
||||
|
||||
return asyncDto;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ICreditNoteCreatedPayload,
|
||||
ICreditNoteCreatingPayload,
|
||||
} 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';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { CreateCreditNoteDto } from '../dtos/CreditNote.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateCreditNoteService {
|
||||
/**
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
* @param {ItemsEntriesService} itemsEntriesService - Items entries service.
|
||||
* @param {EventEmitter2} eventPublisher - Event emitter.
|
||||
* @param {typeof CreditNote} creditNoteModel - Credit note model.
|
||||
* @param {typeof Contact} contactModel - Contact model.
|
||||
* @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - Command credit note DTO transform service.
|
||||
*/
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
|
||||
|
||||
@Inject(CreditNote.name)
|
||||
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
|
||||
|
||||
@Inject(Contact.name)
|
||||
private readonly contactModel: TenantModelProxy<typeof Contact>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new credit note.
|
||||
* @param creditNoteDTO
|
||||
*/
|
||||
public creditCreditNote = async (
|
||||
creditNoteDTO: CreateCreditNoteDto,
|
||||
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);
|
||||
};
|
||||
}
|
||||
@@ -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(): Promise<string> {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
'credit_note',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the credit note serial next number.
|
||||
*/
|
||||
public incrementSerialNumber() {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
'credit_note',
|
||||
);
|
||||
}
|
||||
}
|
||||
185
packages/server/src/modules/CreditNotes/commands/CreditNoteGL.ts
Normal file
185
packages/server/src/modules/CreditNotes/commands/CreditNoteGL.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { CreditNote } from '../models/CreditNote';
|
||||
import { AccountNormal } from '@/interfaces/Account';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||
|
||||
export class CreditNoteGL {
|
||||
creditNoteModel: CreditNote;
|
||||
ARAccountId: number;
|
||||
discountAccountId: number;
|
||||
adjustmentAccountId: number;
|
||||
|
||||
/**
|
||||
* @param {CreditNote} creditNoteModel - Credit note model.
|
||||
*/
|
||||
constructor(creditNoteModel: CreditNote) {
|
||||
this.creditNoteModel = creditNoteModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the A/R account id.
|
||||
* @param {number} ARAccountId - A/R account id.
|
||||
*/
|
||||
public setARAccountId(ARAccountId: number) {
|
||||
this.ARAccountId = ARAccountId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the discount account id.
|
||||
* @param {number} discountAccountId - Discount account id.
|
||||
*/
|
||||
public setDiscountAccountId(discountAccountId: number) {
|
||||
this.discountAccountId = discountAccountId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the adjustment account id.
|
||||
* @param {number} adjustmentAccountId - Adjustment account id.
|
||||
*/
|
||||
public setAdjustmentAccountId(adjustmentAccountId: number) {
|
||||
this.adjustmentAccountId = adjustmentAccountId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the credit note common entry.
|
||||
* @returns {ICreditNoteGLCommonEntry}
|
||||
*/
|
||||
private get creditNoteCommonEntry() {
|
||||
return {
|
||||
date: this.creditNoteModel.creditNoteDate,
|
||||
userId: this.creditNoteModel.userId,
|
||||
currencyCode: this.creditNoteModel.currencyCode,
|
||||
exchangeRate: this.creditNoteModel.exchangeRate,
|
||||
|
||||
transactionType: 'CreditNote',
|
||||
transactionId: this.creditNoteModel.id,
|
||||
|
||||
transactionNumber: this.creditNoteModel.creditNoteNumber,
|
||||
referenceNumber: this.creditNoteModel.referenceNo,
|
||||
|
||||
createdAt: this.creditNoteModel.createdAt,
|
||||
indexGroup: 10,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
branchId: this.creditNoteModel.branchId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the creidt note A/R entry.
|
||||
* @param {ICreditNote} creditNote -
|
||||
* @param {number} ARAccountId -
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get creditNoteAREntry() {
|
||||
const commonEntry = this.creditNoteCommonEntry;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: this.creditNoteModel.totalLocal,
|
||||
accountId: this.ARAccountId,
|
||||
contactId: this.creditNoteModel.customerId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the credit note item entry.
|
||||
* @param {ItemEntry} entry
|
||||
* @param {number} index
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getCreditNoteItemEntry(
|
||||
entry: ItemEntry,
|
||||
index: number,
|
||||
): ILedgerEntry {
|
||||
const commonEntry = this.creditNoteCommonEntry;
|
||||
const totalLocal =
|
||||
entry.totalExcludingTax * this.creditNoteModel.exchangeRate;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: totalLocal,
|
||||
accountId: entry.sellAccountId || entry.item.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit note discount entry.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} discountAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get discountEntry(): ILedgerEntry {
|
||||
const commonEntry = this.creditNoteCommonEntry;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: this.creditNoteModel.discountAmountLocal,
|
||||
accountId: this.discountAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
index: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit note adjustment entry.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} adjustmentAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get adjustmentEntry(): ILedgerEntry {
|
||||
const commonEntry = this.creditNoteCommonEntry;
|
||||
const adjustmentAmount = Math.abs(this.creditNoteModel.adjustmentLocal);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: this.creditNoteModel.adjustmentLocal < 0 ? adjustmentAmount : 0,
|
||||
debit: this.creditNoteModel.adjustmentLocal > 0 ? adjustmentAmount : 0,
|
||||
accountId: this.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(): ILedgerEntry[] {
|
||||
const AREntry = this.creditNoteAREntry;
|
||||
|
||||
const itemsEntries = this.creditNoteModel.entries.map((entry, index) =>
|
||||
this.getCreditNoteItemEntry(entry, index),
|
||||
);
|
||||
const discountEntry = this.discountEntry;
|
||||
const adjustmentEntry = this.adjustmentEntry;
|
||||
|
||||
return [AREntry, discountEntry, adjustmentEntry, ...itemsEntries];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit note GL.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} receivableAccount
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
public getCreditNoteLedger(): Ledger {
|
||||
const ledgerEntries = this.getCreditNoteGLEntries();
|
||||
|
||||
return new Ledger(ledgerEntries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Knex } from 'knex';
|
||||
import { CreditNoteGL } from './CreditNoteGL';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { CreditNote } from '../models/CreditNote';
|
||||
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class CreditNoteGLEntries {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
|
||||
@Inject(CreditNote.name)
|
||||
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reverts the credit note associated GL entries.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public revertVendorCreditGLEntries = async (
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
await this.ledgerStorage.deleteByReference(creditNoteId, 'CreditNote', trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes vendor credit associated GL entries.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @param {Knex.Transaction} trx - Knex transactions.
|
||||
*/
|
||||
public createVendorCreditGLEntries = async (
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// 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 this.accountRepository.findOrCreateAccountReceivable(
|
||||
creditNoteWithItems.currencyCode,
|
||||
);
|
||||
const discountAccount =
|
||||
await this.accountRepository.findOrCreateDiscountAccount({});
|
||||
|
||||
const adjustmentAccount =
|
||||
await this.accountRepository.findOrCreateOtherChargesAccount({});
|
||||
|
||||
const creditNoteLedger = new CreditNoteGL(creditNoteWithItems)
|
||||
.setARAccountId(ARAccount.id)
|
||||
.setDiscountAccountId(discountAccount.id)
|
||||
.setAdjustmentAccountId(adjustmentAccount.id)
|
||||
.getCreditNoteLedger();
|
||||
|
||||
// Saves the credit note GL entries.
|
||||
await this.ledgerStorage.commit(creditNoteLedger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits vendor credit associated GL entries.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public editVendorCreditGLEntries = async (
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Reverts vendor credit GL entries.
|
||||
await this.revertVendorCreditGLEntries(creditNoteId, trx);
|
||||
|
||||
// Creates vendor credit Gl entries.
|
||||
await this.createVendorCreditGLEntries(creditNoteId, trx);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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 [];
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,82 @@
|
||||
// @ts-nocheck
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service';
|
||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||
import { CreditNote } from '../models/CreditNote';
|
||||
import { Knex } from 'knex';
|
||||
@Injectable()
|
||||
export class CreditNoteInventoryTransactions {
|
||||
constructor(
|
||||
private readonly inventoryService: InventoryTransactionsService,
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates credit note inventory transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {ICreditNote} creditNote
|
||||
*/
|
||||
public createInventoryTransactions = async (
|
||||
creditNote: CreditNote,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(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(
|
||||
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 (
|
||||
creditNoteId: number,
|
||||
creditNote: CreditNote,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Deletes inventory transactions.
|
||||
await this.deleteInventoryTransactions(creditNoteId, trx);
|
||||
|
||||
// Re-write inventory transactions.
|
||||
await this.createInventoryTransactions(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 (
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
creditNoteId,
|
||||
'CreditNote',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
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 '../../CreditNotesApplyInvoice/models/CreditNoteAppliedInvoice';
|
||||
import { RefundCreditNote as RefundCreditNoteModel } from '../../CreditNoteRefunds/models/RefundCreditNote';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@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: TenantModelProxy<typeof CreditNote>,
|
||||
|
||||
@Inject(ItemEntry.name)
|
||||
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
|
||||
|
||||
@Inject(CreditNoteAppliedInvoice.name)
|
||||
private readonly creditNoteAppliedInvoiceModel: TenantModelProxy<
|
||||
typeof CreditNoteAppliedInvoice
|
||||
>,
|
||||
|
||||
@Inject(RefundCreditNoteModel.name)
|
||||
private readonly refundCreditNoteModel: TenantModelProxy<
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
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';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { EditCreditNoteDto } from '../dtos/CreditNote.dto';
|
||||
|
||||
@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: TenantModelProxy<typeof CreditNote>,
|
||||
|
||||
@Inject(Contact.name)
|
||||
private contactModel: TenantModelProxy<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: EditCreditNoteDto,
|
||||
) {
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@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: TenantModelProxy<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 = {
|
||||
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)
|
||||
.updateAndFetchById(creditNoteId, {
|
||||
openedAt: new Date(),
|
||||
});
|
||||
// Triggers `onCreditNoteOpened` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onOpened, {
|
||||
...eventPayload,
|
||||
creditNote,
|
||||
} as ICreditNoteOpenedPayload);
|
||||
|
||||
return creditNote;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws an error if the given credit note is already open.
|
||||
* @param {CreditNote} creditNote -
|
||||
*/
|
||||
public throwErrorIfAlreadyOpen = (creditNote: CreditNote) => {
|
||||
if (creditNote.openedAt) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_ALREADY_OPENED);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user