refactor: wip to nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-25 00:43:55 +02:00
parent 336171081e
commit a6932d76f3
249 changed files with 21314 additions and 1616 deletions

View File

@@ -0,0 +1,70 @@
import { Inject, Injectable } from '@nestjs/common';
import moment from 'moment';
import { Knex } from 'knex';
import {
ISaleReceiptEventClosedPayload,
ISaleReceiptEventClosingPayload,
} from '../types/SaleReceipts.types';
import { SaleReceiptValidators } from './SaleReceiptValidators.service';
import { SaleReceipt } from '../models/SaleReceipt';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
@Injectable()
export class CloseSaleReceipt {
/**
* @param {EventEmitter2} eventEmitter - Event emitter.
* @param {UnitOfWork} uow - Unit of work.
* @param {SaleReceiptValidators} validators - Sale receipt validators.
* @param {typeof SaleReceipt} saleReceiptModel - Sale receipt model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: SaleReceiptValidators,
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt,
) {}
/**
* Mark the given sale receipt as closed.
* @param {number} saleReceiptId - Sale receipt identifier.
* @return {Promise<void>}
*/
public async closeSaleReceipt(saleReceiptId: number): Promise<void> {
// Retrieve sale receipt or throw not found service error.
const oldSaleReceipt = await this.saleReceiptModel
.query()
.findById(saleReceiptId)
.withGraphFetched('entries')
.throwIfNotFound();
// Throw service error if the sale receipt already closed.
this.validators.validateReceiptNotClosed(oldSaleReceipt);
// Updates the sale receipt transaction under unit-of-work environment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onSaleReceiptClosing` event.
await this.eventEmitter.emitAsync(events.saleReceipt.onClosing, {
oldSaleReceipt,
trx,
} as ISaleReceiptEventClosingPayload);
// Mark the sale receipt as closed on the storage.
const saleReceipt = await this.saleReceiptModel
.query(trx)
.patchAndFetchById(saleReceiptId, {
closedAt: moment().toMySqlDateTime(),
});
// Triggers `onSaleReceiptClosed` event.
await this.eventEmitter.emitAsync(events.saleReceipt.onClosed, {
saleReceiptId,
saleReceipt,
trx,
} as ISaleReceiptEventClosedPayload);
});
}
}

View File

@@ -0,0 +1,105 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ISaleReceiptCreatedPayload,
ISaleReceiptCreatingPayload,
ISaleReceiptDTO,
} from '../types/SaleReceipts.types';
import { SaleReceiptDTOTransformer } from './SaleReceiptDTOTransformer.service';
import { SaleReceiptValidators } from './SaleReceiptValidators.service';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { SaleReceipt } from '../models/SaleReceipt';
import { Customer } from '@/modules/Customers/models/Customer';
import { events } from '@/common/events/events';
@Injectable()
export class CreateSaleReceipt {
/**
* @param {ItemsEntriesService} itemsEntriesService - Items entries service.
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {UnitOfWork} uow - Unit of work.
* @param {SaleReceiptDTOTransformer} transformer - Sale receipt DTO transformer.
* @param {SaleReceiptValidators} validators - Sale receipt validators.
* @param {typeof SaleReceipt} saleReceiptModel - Sale receipt model.
* @param {typeof Customer} customerModel - Customer model.
*/
constructor(
private readonly itemsEntriesService: ItemsEntriesService,
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly transformer: SaleReceiptDTOTransformer,
private readonly validators: SaleReceiptValidators,
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt,
@Inject(Customer.name)
private readonly customerModel: typeof Customer,
) {}
/**
* Creates a new sale receipt with associated entries.
* @async
* @param {ISaleReceiptDTO} saleReceiptDTO
* @return {Promise<ISaleReceipt>}
*/
public async createSaleReceipt(
saleReceiptDTO: ISaleReceiptDTO,
trx?: Knex.Transaction,
): Promise<SaleReceipt> {
// Retrieves the payment customer model.
const paymentCustomer = await this.customerModel
.query()
.findById(saleReceiptDTO.customerId)
.throwIfNotFound();
// Transform sale receipt DTO to model.
const saleReceiptObj = await this.transformer.transformDTOToModel(
saleReceiptDTO,
paymentCustomer,
);
// Validate receipt deposit account existence and type.
await this.validators.validateReceiptDepositAccountExistence(
saleReceiptDTO.depositAccountId,
);
// Validate items IDs existence on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(
saleReceiptDTO.entries,
);
// Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
saleReceiptDTO.entries,
);
// Validate sale receipt number uniqueness.
if (saleReceiptDTO.receiptNumber) {
await this.validators.validateReceiptNumberUnique(
saleReceiptDTO.receiptNumber,
);
}
// Creates a sale receipt transaction and associated transactions under UOW env.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onSaleReceiptCreating` event.
await this.eventEmitter.emitAsync(events.saleReceipt.onCreating, {
saleReceiptDTO,
trx,
} as ISaleReceiptCreatingPayload);
// Inserts the sale receipt graph to the storage.
const saleReceipt = await this.saleReceiptModel.query().upsertGraph({
...saleReceiptObj,
});
// Triggers `onSaleReceiptCreated` event.
await this.eventEmitter.emitAsync(events.saleReceipt.onCreated, {
saleReceipt,
saleReceiptId: saleReceipt.id,
saleReceiptDTO,
trx,
} as ISaleReceiptCreatedPayload);
return saleReceipt;
}, trx);
}
}

View File

@@ -0,0 +1,69 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ISaleReceiptDeletingPayload,
ISaleReceiptEventDeletedPayload,
} from '../types/SaleReceipts.types';
import { SaleReceiptValidators } from './SaleReceiptValidators.service';
import { SaleReceipt } from '../models/SaleReceipt';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { events } from '@/common/events/events';
@Injectable()
export class DeleteSaleReceipt {
constructor(
private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: SaleReceiptValidators,
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt,
@Inject(ItemEntry.name)
private readonly itemEntryModel: typeof ItemEntry,
) {}
/**
* Deletes the sale receipt with associated entries.
* @param {Integer} saleReceiptId - Sale receipt identifier.
* @return {void}
*/
public async deleteSaleReceipt(saleReceiptId: number) {
const oldSaleReceipt = await this.saleReceiptModel
.query()
.findById(saleReceiptId)
.withGraphFetched('entries');
// Validates the sale receipt existence.
this.validators.validateReceiptExistence(oldSaleReceipt);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onSaleReceiptsDeleting` event.
await this.eventPublisher.emitAsync(events.saleReceipt.onDeleting, {
trx,
oldSaleReceipt,
} as ISaleReceiptDeletingPayload);
await this.itemEntryModel
.query(trx)
.where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt')
.delete();
// Delete the sale receipt transaction.
await this.saleReceiptModel
.query(trx)
.where('id', saleReceiptId)
.delete();
// Triggers `onSaleReceiptsDeleted` event.
await this.eventPublisher.emitAsync(events.saleReceipt.onDeleted, {
saleReceiptId,
oldSaleReceipt,
trx,
} as ISaleReceiptEventDeletedPayload);
});
}
}

View File

@@ -0,0 +1,105 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ISaleReceiptEditedPayload,
ISaleReceiptEditingPayload,
} from '../types/SaleReceipts.types';
import { SaleReceiptValidators } from './SaleReceiptValidators.service';
import { SaleReceiptDTOTransformer } from './SaleReceiptDTOTransformer.service';
import { SaleReceipt } from '../models/SaleReceipt';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Contact } from '@/modules/Contacts/models/Contact';
import { events } from '@/common/events/events';
import { Customer } from '@/modules/Customers/models/Customer';
@Injectable()
export class EditSaleReceipt {
constructor(
private readonly itemsEntriesService: ItemsEntriesService,
private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: SaleReceiptValidators,
private readonly dtoTransformer: SaleReceiptDTOTransformer,
@Inject(SaleReceipt.name)
private readonly saleReceiptModel: typeof SaleReceipt,
@Inject(Customer.name)
private readonly customerModel: typeof Customer,
) {}
/**
* Edit details sale receipt with associated entries.
* @param {Integer} saleReceiptId
* @param {ISaleReceipt} saleReceipt
* @return {void}
*/
public async editSaleReceipt(saleReceiptId: number, saleReceiptDTO: any) {
// Retrieve sale receipt or throw not found service error.
const oldSaleReceipt = await this.saleReceiptModel
.query()
.findById(saleReceiptId)
.withGraphFetched('entries')
.throwIfNotFound();
// Retrieves the payment customer model.
const paymentCustomer = await this.customerModel
.query()
.findById(saleReceiptDTO.customerId)
.throwIfNotFound();
// Transform sale receipt DTO to model.
const saleReceiptObj = await this.dtoTransformer.transformDTOToModel(
saleReceiptDTO,
paymentCustomer,
oldSaleReceipt,
);
// Validate receipt deposit account existance and type.
await this.validators.validateReceiptDepositAccountExistence(
saleReceiptDTO.depositAccountId,
);
// Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(
saleReceiptDTO.entries,
);
// Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
saleReceiptDTO.entries,
);
// Validate sale receipt number uniuqiness.
if (saleReceiptDTO.receiptNumber) {
await this.validators.validateReceiptNumberUnique(
saleReceiptDTO.receiptNumber,
saleReceiptId,
);
}
// Edits the sale receipt tranasctions with associated transactions under UOW env.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onSaleReceiptsEditing` event.
await this.eventPublisher.emitAsync(events.saleReceipt.onEditing, {
oldSaleReceipt,
saleReceiptDTO,
trx,
} as ISaleReceiptEditingPayload);
// Upsert the receipt graph to the storage.
const saleReceipt = await this.saleReceiptModel
.query(trx)
.upsertGraphAndFetch({
id: saleReceiptId,
...saleReceiptObj,
});
// Triggers `onSaleReceiptEdited` event.
await this.eventPublisher.emitAsync(events.saleReceipt.onEdited, {
oldSaleReceipt,
saleReceipt,
saleReceiptDTO,
trx,
} as ISaleReceiptEditedPayload);
return saleReceipt;
});
}
}

View File

@@ -0,0 +1,148 @@
// import { Service, Inject } from 'typedi';
// import * as R from 'ramda';
// import { Knex } from 'knex';
// import { AccountNormal, IInventoryLotCost, ILedgerEntry } from '@/interfaces';
// import { increment } from 'utils';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import Ledger from '@/services/Accounting/Ledger';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import { groupInventoryTransactionsByTypeId } from '../../Inventory/utils';
// @Service()
// export class SaleReceiptCostGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// /**
// * Writes journal entries from sales invoices.
// * @param {number} tenantId - The tenant id.
// * @param {Date} startingDate - Starting date.
// * @param {boolean} override
// */
// public writeInventoryCostJournalEntries = async (
// tenantId: number,
// startingDate: Date,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
// const inventoryCostLotTrans = await InventoryCostLotTracker.query()
// .where('direction', 'OUT')
// .where('transaction_type', 'SaleReceipt')
// .where('cost', '>', 0)
// .modify('filterDateRange', startingDate)
// .orderBy('date', 'ASC')
// .withGraphFetched('receipt')
// .withGraphFetched('item');
// const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
// // Commit the ledger to the storage.
// await this.ledgerStorage.commit(tenantId, ledger, trx);
// };
// /**
// * Retrieves the inventory cost lots ledger.
// * @param {} inventoryCostLots
// * @returns {Ledger}
// */
// private getInventoryCostLotsLedger = (
// inventoryCostLots: IInventoryLotCost[]
// ) => {
// // Groups the inventory cost lots transactions.
// const inventoryTransactions =
// groupInventoryTransactionsByTypeId(inventoryCostLots);
// //
// const entries = inventoryTransactions
// .map(this.getSaleInvoiceCostGLEntries)
// .flat();
// return new Ledger(entries);
// };
// /**
// *
// * @param {IInventoryLotCost} inventoryCostLot
// * @returns {}
// */
// private getInvoiceCostGLCommonEntry = (
// inventoryCostLot: IInventoryLotCost
// ) => {
// return {
// currencyCode: inventoryCostLot.receipt.currencyCode,
// exchangeRate: inventoryCostLot.receipt.exchangeRate,
// transactionType: inventoryCostLot.transactionType,
// transactionId: inventoryCostLot.transactionId,
// date: inventoryCostLot.date,
// indexGroup: 20,
// costable: true,
// createdAt: inventoryCostLot.createdAt,
// debit: 0,
// credit: 0,
// branchId: inventoryCostLot.receipt.branchId,
// };
// };
// /**
// * Retrieves the inventory cost GL entry.
// * @param {IInventoryLotCost} inventoryLotCost
// * @returns {ILedgerEntry[]}
// */
// private getInventoryCostGLEntry = R.curry(
// (
// getIndexIncrement,
// inventoryCostLot: IInventoryLotCost
// ): ILedgerEntry[] => {
// const commonEntry = this.getInvoiceCostGLCommonEntry(inventoryCostLot);
// const costAccountId =
// inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
// // XXX Debit - Cost account.
// const costEntry = {
// ...commonEntry,
// debit: inventoryCostLot.cost,
// accountId: costAccountId,
// accountNormal: AccountNormal.DEBIT,
// itemId: inventoryCostLot.itemId,
// index: getIndexIncrement(),
// };
// // XXX Credit - Inventory account.
// const inventoryEntry = {
// ...commonEntry,
// credit: inventoryCostLot.cost,
// accountId: inventoryCostLot.item.inventoryAccountId,
// accountNormal: AccountNormal.DEBIT,
// itemId: inventoryCostLot.itemId,
// index: getIndexIncrement(),
// };
// return [costEntry, inventoryEntry];
// }
// );
// /**
// * Writes journal entries for given sale invoice.
// * -------
// * - Cost of goods sold -> Debit -> YYYY
// * - Inventory assets -> Credit -> YYYY
// * --------
// * @param {ISaleInvoice} saleInvoice
// * @param {JournalPoster} journal
// */
// public getSaleInvoiceCostGLEntries = (
// inventoryCostLots: IInventoryLotCost[]
// ): ILedgerEntry[] => {
// const getIndexIncrement = increment(0);
// const getInventoryLotEntry =
// this.getInventoryCostGLEntry(getIndexIncrement);
// return inventoryCostLots.map(getInventoryLotEntry).flat();
// };
// }

View File

@@ -0,0 +1,102 @@
import { Inject, Injectable } from '@nestjs/common';
import * as R from 'ramda';
import { sumBy, omit } from 'lodash';
import composeAsync from 'async/compose';
import moment from 'moment';
import { SaleReceiptIncrement } from './SaleReceiptIncrement.service';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/modules/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { SaleReceiptValidators } from './SaleReceiptValidators.service';
import { BrandingTemplateDTOTransformer } from '@/modules/PdfTemplate/BrandingTemplateDTOTransformer';
import { ItemEntry } from '@/modules/Items/models/ItemEntry';
import { formatDateFields } from '@/utils/format-date-fields';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { SaleReceipt } from '../models/SaleReceipt';
import { ISaleReceiptDTO } from '../types/SaleReceipts.types';
import { Customer } from '@/modules/Customers/models/Customer';
@Injectable()
export class SaleReceiptDTOTransformer {
constructor(
private readonly itemsEntriesService: ItemsEntriesService,
private readonly branchDTOTransform: BranchTransactionDTOTransformer,
private readonly warehouseDTOTransform: WarehouseTransactionDTOTransform,
private readonly validators: SaleReceiptValidators,
private readonly receiptIncrement: SaleReceiptIncrement,
private readonly brandingTemplatesTransformer: BrandingTemplateDTOTransformer,
@Inject(ItemEntry.name)
private readonly itemEntryModel: typeof ItemEntry,
) {}
/**
* Transform create DTO object to model object.
* @param {ISaleReceiptDTO} saleReceiptDTO -
* @param {ISaleReceipt} oldSaleReceipt -
* @returns {ISaleReceipt}
*/
async transformDTOToModel(
saleReceiptDTO: ISaleReceiptDTO,
paymentCustomer: Customer,
oldSaleReceipt?: SaleReceipt,
): Promise<SaleReceipt> {
const amount = sumBy(saleReceiptDTO.entries, (e) =>
this.itemEntryModel.calcAmount(e),
);
// Retrieve the next invoice number.
const autoNextNumber = await this.receiptIncrement.getNextReceiptNumber();
// Retrieve the receipt number.
const receiptNumber =
saleReceiptDTO.receiptNumber ||
oldSaleReceipt?.receiptNumber ||
autoNextNumber;
// Validate receipt number require.
this.validators.validateReceiptNoRequire(receiptNumber);
const initialEntries = saleReceiptDTO.entries.map((entry) => ({
reference_type: 'SaleReceipt',
...entry,
}));
const asyncEntries = await composeAsync(
// Sets default cost and sell account to receipt items entries.
this.itemsEntriesService.setItemsEntriesDefaultAccounts(),
)(initialEntries);
const entries = R.compose(
// Associate the default index for each item entry.
assocItemEntriesDefaultIndex,
)(asyncEntries);
const initialDTO = {
amount,
...formatDateFields(
omit(saleReceiptDTO, ['closed', 'entries', 'attachments']),
['receiptDate'],
),
currencyCode: paymentCustomer.currencyCode,
exchangeRate: saleReceiptDTO.exchangeRate || 1,
receiptNumber,
// Avoid rewrite the deliver date in edit mode when already published.
...(saleReceiptDTO.closed &&
!oldSaleReceipt?.closedAt && {
closedAt: moment().toMySqlDateTime(),
}),
entries,
};
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
'SaleReceipt',
),
)(initialDTO);
return R.compose(
this.branchDTOTransform.transformDTO<SaleReceipt>,
this.warehouseDTOTransform.transformDTO<SaleReceipt>,
)(initialAsyncDTO);
}
}

View File

@@ -0,0 +1,184 @@
// import { Knex } from 'knex';
// import { Service, Inject } from 'typedi';
// import * as R from 'ramda';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
// import {
// AccountNormal,
// ILedgerEntry,
// ISaleReceipt,
// IItemEntry,
// } from '@/interfaces';
// import Ledger from '@/services/Accounting/Ledger';
// @Service()
// export class SaleReceiptGLEntries {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private ledgerStorage: LedgerStorageService;
// /**
// * Creates income GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// */
// public writeIncomeGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// const { SaleReceipt } = this.tenancy.models(tenantId);
// const saleReceipt = await SaleReceipt.query(trx)
// .findById(saleReceiptId)
// .withGraphFetched('entries.item');
// // Retrieve the income entries ledger.
// const incomeLedger = this.getIncomeEntriesLedger(saleReceipt);
// // Commits the ledger entries to the storage.
// await this.ledgerStorage.commit(tenantId, incomeLedger, trx);
// };
// /**
// * Reverts the receipt GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public revertReceiptGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// await this.ledgerStorage.deleteByReference(
// tenantId,
// saleReceiptId,
// 'SaleReceipt',
// trx
// );
// };
// /**
// * Rewrites the receipt GL entries.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {Knex.Transaction} trx
// * @returns {Promise<void>}
// */
// public rewriteReceiptGLEntries = async (
// tenantId: number,
// saleReceiptId: number,
// trx?: Knex.Transaction
// ): Promise<void> => {
// // Reverts the receipt GL entries.
// await this.revertReceiptGLEntries(tenantId, saleReceiptId, trx);
// // Writes the income GL entries.
// await this.writeIncomeGLEntries(tenantId, saleReceiptId, trx);
// };
// /**
// * Retrieves the income GL ledger.
// * @param {ISaleReceipt} saleReceipt
// * @returns {Ledger}
// */
// private getIncomeEntriesLedger = (saleReceipt: ISaleReceipt): Ledger => {
// const entries = this.getIncomeGLEntries(saleReceipt);
// return new Ledger(entries);
// };
// /**
// * Retireves the income GL common entry.
// * @param {ISaleReceipt} saleReceipt -
// */
// private getIncomeGLCommonEntry = (saleReceipt: ISaleReceipt) => {
// return {
// currencyCode: saleReceipt.currencyCode,
// exchangeRate: saleReceipt.exchangeRate,
// transactionType: 'SaleReceipt',
// transactionId: saleReceipt.id,
// date: saleReceipt.receiptDate,
// transactionNumber: saleReceipt.receiptNumber,
// referenceNumber: saleReceipt.referenceNo,
// createdAt: saleReceipt.createdAt,
// credit: 0,
// debit: 0,
// userId: saleReceipt.userId,
// branchId: saleReceipt.branchId,
// };
// };
// /**
// * Retrieve receipt income item GL entry.
// * @param {ISaleReceipt} saleReceipt -
// * @param {IItemEntry} entry -
// * @param {number} index -
// * @returns {ILedgerEntry}
// */
// private getReceiptIncomeItemEntry = R.curry(
// (
// saleReceipt: ISaleReceipt,
// entry: IItemEntry,
// index: number
// ): ILedgerEntry => {
// const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
// const itemIncome = entry.amount * saleReceipt.exchangeRate;
// return {
// ...commonEntry,
// credit: itemIncome,
// accountId: entry.item.sellAccountId,
// note: entry.description,
// index: index + 2,
// itemId: entry.itemId,
// itemQuantity: entry.quantity,
// accountNormal: AccountNormal.CREDIT,
// };
// }
// );
// /**
// * Retrieves the receipt deposit GL deposit entry.
// * @param {ISaleReceipt} saleReceipt
// * @returns {ILedgerEntry}
// */
// private getReceiptDepositEntry = (
// saleReceipt: ISaleReceipt
// ): ILedgerEntry => {
// const commonEntry = this.getIncomeGLCommonEntry(saleReceipt);
// return {
// ...commonEntry,
// debit: saleReceipt.localAmount,
// accountId: saleReceipt.depositAccountId,
// index: 1,
// accountNormal: AccountNormal.DEBIT,
// };
// };
// /**
// * Retrieves the income GL entries.
// * @param {ISaleReceipt} saleReceipt -
// * @returns {ILedgerEntry[]}
// */
// private getIncomeGLEntries = (saleReceipt: ISaleReceipt): ILedgerEntry[] => {
// const getItemEntry = this.getReceiptIncomeItemEntry(saleReceipt);
// const creditEntries = saleReceipt.entries.map(getItemEntry);
// const depositEntry = this.getReceiptDepositEntry(saleReceipt);
// return [depositEntry, ...creditEntries];
// };
// }

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import { AutoIncrementOrdersService } from '@/modules/AutoIncrementOrders/AutoIncrementOrders.service';
@Injectable()
export class SaleReceiptIncrement {
constructor(
private readonly autoIncrementOrdersService: AutoIncrementOrdersService,
) {}
/**
* Retrieve the next unique receipt number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
public getNextReceiptNumber(): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
'sales_receipts',
);
}
/**
* Increment the receipt next number.
* @param {number} tenantId -
*/
public incrementNextReceiptNumber() {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
'sales_receipts',
);
}
}

View File

@@ -0,0 +1,72 @@
// import { Knex } from 'knex';
// import { Inject, Service } from 'typedi';
// import { ISaleReceipt } from '@/interfaces';
// import InventoryService from '@/services/Inventory/Inventory';
// import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
// @Service()
// export class SaleReceiptInventoryTransactions {
// @Inject()
// private inventoryService: InventoryService;
// @Inject()
// private itemsEntriesService: ItemsEntriesService;
// /**
// * Records the inventory transactions from the given bill input.
// * @param {Bill} bill - Bill model object.
// * @param {number} billId - Bill id.
// * @return {Promise<void>}
// */
// public async recordInventoryTransactions(
// tenantId: number,
// saleReceipt: ISaleReceipt,
// override?: boolean,
// trx?: Knex.Transaction
// ): Promise<void> {
// // Loads the inventory items entries of the given sale invoice.
// const inventoryEntries =
// await this.itemsEntriesService.filterInventoryEntries(
// tenantId,
// saleReceipt.entries
// );
// const transaction = {
// transactionId: saleReceipt.id,
// transactionType: 'SaleReceipt',
// transactionNumber: saleReceipt.receiptNumber,
// exchangeRate: saleReceipt.exchangeRate,
// date: saleReceipt.receiptDate,
// direction: 'OUT',
// entries: inventoryEntries,
// createdAt: saleReceipt.createdAt,
// warehouseId: saleReceipt.warehouseId,
// };
// return this.inventoryService.recordInventoryTransactionsFromItemsEntries(
// tenantId,
// transaction,
// override,
// trx
// );
// }
// /**
// * Reverts the inventory transactions of the given bill id.
// * @param {number} tenantId - Tenant id.
// * @param {number} billId - Bill id.
// * @return {Promise<void>}
// */
// public async revertInventoryTransactions(
// tenantId: number,
// receiptId: number,
// trx?: Knex.Transaction
// ) {
// return this.inventoryService.deleteInventoryTransactions(
// tenantId,
// receiptId,
// 'SaleReceipt',
// trx
// );
// }
// }

View File

@@ -0,0 +1,220 @@
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import { Inject, Service } from 'typedi';
// import Mail from '@/lib/Mail';
// import { GetSaleReceipt } from '../queries/GetSaleReceipt';
// import { SaleReceiptsPdf } from '../queries/SaleReceiptsPdfService';
// import {
// DEFAULT_RECEIPT_MAIL_CONTENT,
// DEFAULT_RECEIPT_MAIL_SUBJECT,
// } from '../constants';
// import {
// ISaleReceiptMailPresend,
// SaleReceiptMailOpts,
// SaleReceiptMailOptsDTO,
// } from '@/interfaces';
// import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
// import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
// import events from '@/subscribers/events';
// import { transformReceiptToMailDataArgs } from '../utils';
// @Service()
// export class SaleReceiptMailNotification {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private getSaleReceiptService: GetSaleReceipt;
// @Inject()
// private receiptPdfService: SaleReceiptsPdf;
// @Inject()
// private contactMailNotification: ContactMailNotification;
// @Inject()
// private eventPublisher: EventPublisher;
// @Inject('agenda')
// private agenda: any;
// /**
// * Sends the receipt mail of the given sale receipt.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {SaleReceiptMailOptsDTO} messageDTO
// */
// public async triggerMail(
// tenantId: number,
// saleReceiptId: number,
// messageOptions: SaleReceiptMailOptsDTO
// ) {
// const payload = {
// tenantId,
// saleReceiptId,
// messageOpts: messageOptions,
// };
// await this.agenda.now('sale-receipt-mail-send', payload);
// // Triggers the event `onSaleReceiptPreMailSend`.
// await this.eventPublisher.emitAsync(events.saleReceipt.onPreMailSend, {
// tenantId,
// saleReceiptId,
// messageOptions,
// } as ISaleReceiptMailPresend);
// }
// /**
// * Retrieves the mail options of the given sale receipt.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @returns {Promise<SaleReceiptMailOptsDTO>}
// */
// public async getMailOptions(
// tenantId: number,
// saleReceiptId: number
// ): Promise<SaleReceiptMailOpts> {
// const { SaleReceipt } = this.tenancy.models(tenantId);
// const saleReceipt = await SaleReceipt.query()
// .findById(saleReceiptId)
// .throwIfNotFound();
// const formatArgs = await this.textFormatterArgs(tenantId, saleReceiptId);
// const mailOptions =
// await this.contactMailNotification.getDefaultMailOptions(
// tenantId,
// saleReceipt.customerId
// );
// return {
// ...mailOptions,
// message: DEFAULT_RECEIPT_MAIL_CONTENT,
// subject: DEFAULT_RECEIPT_MAIL_SUBJECT,
// attachReceipt: true,
// formatArgs,
// };
// }
// /**
// * Retrieves the formatted text of the given sale receipt.
// * @param {number} tenantId - Tenant id.
// * @param {number} receiptId - Sale receipt id.
// * @param {string} text - The given text.
// * @returns {Promise<string>}
// */
// public textFormatterArgs = async (
// tenantId: number,
// receiptId: number
// ): Promise<Record<string, string>> => {
// const receipt = await this.getSaleReceiptService.getSaleReceipt(
// tenantId,
// receiptId
// );
// return transformReceiptToMailDataArgs(receipt);
// };
// /**
// * Formats the mail options of the given sale receipt.
// * @param {number} tenantId
// * @param {number} receiptId
// * @param {SaleReceiptMailOpts} mailOptions
// * @returns {Promise<SaleReceiptMailOpts>}
// */
// public async formatEstimateMailOptions(
// tenantId: number,
// receiptId: number,
// mailOptions: SaleReceiptMailOpts
// ): Promise<SaleReceiptMailOpts> {
// const formatterArgs = await this.textFormatterArgs(tenantId, receiptId);
// const formattedOptions =
// (await this.contactMailNotification.formatMailOptions(
// tenantId,
// mailOptions,
// formatterArgs
// )) as SaleReceiptMailOpts;
// return formattedOptions;
// }
// /**
// * Retrieves the formatted mail options of the given sale receipt.
// * @param {number} tenantId
// * @param {number} saleReceiptId
// * @param {SaleReceiptMailOptsDTO} messageOpts
// * @returns {Promise<SaleReceiptMailOpts>}
// */
// public getFormatMailOptions = async (
// tenantId: number,
// saleReceiptId: number,
// messageOpts: SaleReceiptMailOptsDTO
// ): Promise<SaleReceiptMailOpts> => {
// const defaultMessageOptions = await this.getMailOptions(
// tenantId,
// saleReceiptId
// );
// // Merges message opts with default options.
// const parsedMessageOpts = mergeAndValidateMailOptions(
// defaultMessageOptions,
// messageOpts
// ) as SaleReceiptMailOpts;
// // Formats the message options.
// return this.formatEstimateMailOptions(
// tenantId,
// saleReceiptId,
// parsedMessageOpts
// );
// };
// /**
// * Triggers the mail notification of the given sale receipt.
// * @param {number} tenantId - Tenant id.
// * @param {number} saleReceiptId - Sale receipt id.
// * @param {SaleReceiptMailOpts} messageDTO - message options.
// * @returns {Promise<void>}
// */
// public async sendMail(
// tenantId: number,
// saleReceiptId: number,
// messageOpts: SaleReceiptMailOptsDTO
// ) {
// // Formats the message options.
// const formattedMessageOptions = await this.getFormatMailOptions(
// tenantId,
// saleReceiptId,
// messageOpts
// );
// const mail = new Mail()
// .setSubject(formattedMessageOptions.subject)
// .setTo(formattedMessageOptions.to)
// .setCC(formattedMessageOptions.cc)
// .setBCC(formattedMessageOptions.bcc)
// .setContent(formattedMessageOptions.message);
// // Attaches the receipt pdf document.
// if (formattedMessageOptions.attachReceipt) {
// // Retrieves document buffer of the receipt pdf document.
// const [receiptPdfBuffer, filename] =
// await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId);
// mail.setAttachments([
// { filename: `${filename}.pdf`, content: receiptPdfBuffer },
// ]);
// }
// const eventPayload = {
// tenantId,
// saleReceiptId,
// messageOptions: {},
// };
// await this.eventPublisher.emitAsync(
// events.saleReceipt.onMailSend,
// eventPayload
// );
// await mail.send();
// await this.eventPublisher.emitAsync(
// events.saleReceipt.onMailSent,
// eventPayload
// );
// }
// }

View File

@@ -0,0 +1,36 @@
// import Container, { Service } from 'typedi';
// import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
// @Service()
// export class SaleReceiptMailNotificationJob {
// /**
// * Constructor method.
// */
// constructor(agenda) {
// agenda.define(
// 'sale-receipt-mail-send',
// { priority: 'high', concurrency: 2 },
// this.handler
// );
// }
// /**
// * Triggers sending invoice mail.
// */
// private handler = async (job, done: Function) => {
// const { tenantId, saleReceiptId, messageOpts } = job.attrs.data;
// const receiveMailNotification = Container.get(SaleReceiptMailNotification);
// try {
// await receiveMailNotification.sendMail(
// tenantId,
// saleReceiptId,
// messageOpts
// );
// done();
// } catch (error) {
// console.log(error);
// done(error);
// }
// };
// }

View File

@@ -0,0 +1,206 @@
// import { Service, Inject } from 'typedi';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import events from '@/subscribers/events';
// import {
// ISaleReceiptSmsDetails,
// ISaleReceipt,
// SMS_NOTIFICATION_KEY,
// ICustomer,
// } from '@/interfaces';
// import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
// import { formatNumber, formatSmsMessage } from 'utils';
// import { TenantMetadata } from '@/system/models';
// import { ServiceError } from '@/exceptions';
// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
// import SaleNotifyBySms from '../SaleNotifyBySms';
// import { ERRORS } from './constants';
// @Service()
// export class SaleReceiptNotifyBySms {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private eventPublisher: EventPublisher;
// @Inject()
// private smsNotificationsSettings: SmsNotificationsSettingsService;
// @Inject()
// private saleSmsNotification: SaleNotifyBySms;
// /**
// * Notify customer via sms about sale receipt.
// * @param {number} tenantId - Tenant id.
// * @param {number} saleReceiptId - Sale receipt id.
// */
// public async notifyBySms(tenantId: number, saleReceiptId: number) {
// const { SaleReceipt } = this.tenancy.models(tenantId);
// // Retrieve the sale receipt or throw not found service error.
// const saleReceipt = await SaleReceipt.query()
// .findById(saleReceiptId)
// .withGraphFetched('customer');
// // Validates the receipt receipt existance.
// this.validateSaleReceiptExistance(saleReceipt);
// // Validate the customer phone number.
// this.saleSmsNotification.validateCustomerPhoneNumber(
// saleReceipt.customer.personalPhone
// );
// // Triggers `onSaleReceiptNotifySms` event.
// await this.eventPublisher.emitAsync(events.saleReceipt.onNotifySms, {
// tenantId,
// saleReceipt,
// });
// // Sends the payment receive sms notification to the given customer.
// await this.sendSmsNotification(tenantId, saleReceipt);
// // Triggers `onSaleReceiptNotifiedSms` event.
// await this.eventPublisher.emitAsync(events.saleReceipt.onNotifiedSms, {
// tenantId,
// saleReceipt,
// });
// return saleReceipt;
// }
// /**
// * Sends SMS notification.
// * @param {ISaleReceipt} invoice
// * @param {ICustomer} customer
// * @returns
// */
// public sendSmsNotification = async (
// tenantId: number,
// saleReceipt: ISaleReceipt & { customer: ICustomer }
// ) => {
// const smsClient = this.tenancy.smsClient(tenantId);
// const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// // Retrieve formatted sms notification message of receipt details.
// const formattedSmsMessage = this.formattedReceiptDetailsMessage(
// tenantId,
// saleReceipt,
// tenantMetadata
// );
// const phoneNumber = saleReceipt.customer.personalPhone;
// // Run the send sms notification message job.
// return smsClient.sendMessageJob(phoneNumber, formattedSmsMessage);
// };
// /**
// * Notify via SMS message after receipt creation.
// * @param {number} tenantId
// * @param {number} receiptId
// * @returns {Promise<void>}
// */
// public notifyViaSmsAfterCreation = async (
// tenantId: number,
// receiptId: number
// ): Promise<void> => {
// const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
// tenantId,
// SMS_NOTIFICATION_KEY.SALE_RECEIPT_DETAILS
// );
// // Can't continue if the sms auto-notification is not enabled.
// if (!notification.isNotificationEnabled) return;
// await this.notifyBySms(tenantId, receiptId);
// };
// /**
// * Retrieve the formatted sms notification message of the given sale receipt.
// * @param {number} tenantId
// * @param {ISaleReceipt} saleReceipt
// * @param {TenantMetadata} tenantMetadata
// * @returns {string}
// */
// private formattedReceiptDetailsMessage = (
// tenantId: number,
// saleReceipt: ISaleReceipt & { customer: ICustomer },
// tenantMetadata: TenantMetadata
// ): string => {
// const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
// tenantId,
// SMS_NOTIFICATION_KEY.SALE_RECEIPT_DETAILS
// );
// return this.formatReceiptDetailsMessage(
// notification.smsMessage,
// saleReceipt,
// tenantMetadata
// );
// };
// /**
// * Formattes the receipt sms notification message.
// * @param {string} smsMessage
// * @param {ISaleReceipt} saleReceipt
// * @param {TenantMetadata} tenantMetadata
// * @returns {string}
// */
// private formatReceiptDetailsMessage = (
// smsMessage: string,
// saleReceipt: ISaleReceipt & { customer: ICustomer },
// tenantMetadata: TenantMetadata
// ): string => {
// // Format the receipt amount.
// const formattedAmount = formatNumber(saleReceipt.amount, {
// currencyCode: saleReceipt.currencyCode,
// });
// return formatSmsMessage(smsMessage, {
// ReceiptNumber: saleReceipt.receiptNumber,
// ReferenceNumber: saleReceipt.referenceNo,
// CustomerName: saleReceipt.customer.displayName,
// Amount: formattedAmount,
// CompanyName: tenantMetadata.name,
// });
// };
// /**
// * Retrieve the SMS details of the given invoice.
// * @param {number} tenantId -
// * @param {number} saleReceiptId - Sale receipt id.
// */
// public smsDetails = async (
// tenantId: number,
// saleReceiptId: number
// ): Promise<ISaleReceiptSmsDetails> => {
// const { SaleReceipt } = this.tenancy.models(tenantId);
// // Retrieve the sale receipt or throw not found service error.
// const saleReceipt = await SaleReceipt.query()
// .findById(saleReceiptId)
// .withGraphFetched('customer');
// // Validates the receipt receipt existance.
// this.validateSaleReceiptExistance(saleReceipt);
// // Current tenant metadata.
// const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// // Retrieve the sale receipt formatted sms notification message.
// const formattedSmsMessage = this.formattedReceiptDetailsMessage(
// tenantId,
// saleReceipt,
// tenantMetadata
// );
// return {
// customerName: saleReceipt.customer.displayName,
// customerPhoneNumber: saleReceipt.customer.personalPhone,
// smsMessage: formattedSmsMessage,
// };
// };
// /**
// * Validates the receipt receipt existance.
// * @param {ISaleReceipt|null} saleReceipt
// */
// private validateSaleReceiptExistance(saleReceipt: ISaleReceipt | null) {
// if (!saleReceipt) {
// throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND);
// }
// }
// }

View File

@@ -0,0 +1,100 @@
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { SaleReceipt } from '../models/SaleReceipt';
import { Account } from '@/modules/Accounts/models/Account.model';
import { ServiceError } from '@/modules/Items/ServiceError';
import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts';
@Injectable()
export class SaleReceiptValidators {
/**
* @param {typeof SaleReceipt} saleReceiptModel - Sale receipt model.
* @param {typeof Account} accountModel - Account model.
*/
constructor(
@Inject(SaleReceipt) private saleReceiptModel: typeof SaleReceipt,
@Inject(Account) private accountModel: typeof Account,
) {}
/**
* Validates the sale receipt existence.
* @param {SaleEstimate | undefined | null} estimate
*/
public validateReceiptExistence(receipt: SaleReceipt | undefined | null) {
if (!receipt) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND);
}
}
/**
* Validates the receipt not closed.
* @param {SaleReceipt} receipt
*/
public validateReceiptNotClosed(receipt: SaleReceipt) {
if (receipt.isClosed) {
throw new ServiceError(ERRORS.SALE_RECEIPT_IS_ALREADY_CLOSED);
}
}
/**
* Validate whether sale receipt deposit account exists on the storage.
* @param {number} accountId - Account id.
*/
public async validateReceiptDepositAccountExistence(accountId: number) {
const depositAccount = await this.accountModel.query().findById(accountId);
if (!depositAccount) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
}
if (!depositAccount.isParentType(ACCOUNT_PARENT_TYPE.CURRENT_ASSET)) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET);
}
}
/**
* Validate sale receipt number uniqueness on the storage.
* @param {string} receiptNumber -
* @param {number} notReceiptId -
*/
public async validateReceiptNumberUnique(
receiptNumber: string,
notReceiptId?: number,
) {
const saleReceipt = await this.saleReceiptModel
.query()
.findOne('receipt_number', receiptNumber)
.onBuild((builder) => {
if (notReceiptId) {
builder.whereNot('id', notReceiptId);
}
});
if (saleReceipt) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NUMBER_NOT_UNIQUE);
}
}
/**
* Validate the sale receipt number require.
* @param {ISaleReceipt} saleReceipt
*/
public validateReceiptNoRequire(receiptNumber: string) {
if (!receiptNumber) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NO_IS_REQUIRED);
}
}
/**
* Validate the given customer has no sales receipts.
* @param {number} customerId - Customer id.
*/
public async validateCustomerHasNoReceipts(customerId: number) {
const receipts = await this.saleReceiptModel
.query()
.where('customer_id', customerId);
if (receipts.length > 0) {
throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_INVOICES);
}
}
}

View File

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

View File

@@ -0,0 +1,45 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { IAccountCreateDTO, ISaleReceiptDTO } from '@/interfaces';
// import { CreateSaleReceipt } from './commands/CreateSaleReceipt.service';
// import { Importable } from '@/services/Import/Importable';
// import { SaleReceiptsSampleData } from './constants';
// @Service()
// export class SaleReceiptsImportable extends Importable {
// @Inject()
// private createReceiptService: CreateSaleReceipt;
// /**
// * Importing to sale receipts service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: ISaleReceiptDTO,
// trx?: Knex.Transaction
// ) {
// return this.createReceiptService.createSaleReceipt(
// 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 SaleReceiptsSampleData;
// }
// }