mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
refactor: split the services to multiple service classes (#202)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import {
|
||||
ISaleReceiptEventClosedPayload,
|
||||
ISaleReceiptEventClosingPayload,
|
||||
} from '@/interfaces';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
|
||||
@Service()
|
||||
export class CloseSaleReceipt {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
/**
|
||||
* Mark the given sale receipt as closed.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async closeSaleReceipt(
|
||||
tenantId: number,
|
||||
saleReceiptId: number
|
||||
): Promise<void> {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve sale receipt or throw not found service error.
|
||||
const oldSaleReceipt = await SaleReceipt.query()
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Throw service error if the sale receipt already closed.
|
||||
this.validators.validateReceiptNotClosed(oldSaleReceipt);
|
||||
|
||||
// Updates the sale recept transaction under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleReceiptClosing` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onClosing, {
|
||||
tenantId,
|
||||
oldSaleReceipt,
|
||||
trx,
|
||||
} as ISaleReceiptEventClosingPayload);
|
||||
|
||||
// Mark the sale receipt as closed on the storage.
|
||||
const saleReceipt = await SaleReceipt.query(trx)
|
||||
.findById(saleReceiptId)
|
||||
.patch({
|
||||
closedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
|
||||
// Triggers `onSaleReceiptClosed` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onClosed, {
|
||||
saleReceiptId,
|
||||
saleReceipt,
|
||||
tenantId,
|
||||
trx,
|
||||
} as ISaleReceiptEventClosedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
106
packages/server/src/services/Sales/Receipts/CreateSaleReceipt.ts
Normal file
106
packages/server/src/services/Sales/Receipts/CreateSaleReceipt.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ISaleReceipt,
|
||||
ISaleReceiptCreatedPayload,
|
||||
ISaleReceiptCreatingPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { SaleReceiptDTOTransformer } from './SaleReceiptDTOTransformer';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
|
||||
@Service()
|
||||
export class CreateSaleReceipt {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private transformer: SaleReceiptDTOTransformer;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
/**
|
||||
* Creates a new sale receipt with associated entries.
|
||||
* @async
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
* @return {Object}
|
||||
*/
|
||||
public async createSaleReceipt(
|
||||
tenantId: number,
|
||||
saleReceiptDTO: any
|
||||
): Promise<ISaleReceipt> {
|
||||
const { SaleReceipt, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireves the payment customer model.
|
||||
const paymentCustomer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(saleReceiptDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform sale receipt DTO to model.
|
||||
const saleReceiptObj = await this.transformer.transformDTOToModel(
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
paymentCustomer
|
||||
);
|
||||
// Validate receipt deposit account existance and type.
|
||||
await this.validators.validateReceiptDepositAccountExistance(
|
||||
tenantId,
|
||||
saleReceiptDTO.depositAccountId
|
||||
);
|
||||
// Validate items IDs existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleReceiptDTO.entries
|
||||
);
|
||||
// Validate the sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleReceiptDTO.entries
|
||||
);
|
||||
// Validate sale receipt number uniuqiness.
|
||||
if (saleReceiptDTO.receiptNumber) {
|
||||
await this.validators.validateReceiptNumberUnique(
|
||||
tenantId,
|
||||
saleReceiptDTO.receiptNumber
|
||||
);
|
||||
}
|
||||
// Creates a sale receipt transaction and associated transactions under UOW env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleReceiptCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onCreating, {
|
||||
saleReceiptDTO,
|
||||
tenantId,
|
||||
trx,
|
||||
} as ISaleReceiptCreatingPayload);
|
||||
|
||||
// Inserts the sale receipt graph to the storage.
|
||||
const saleReceipt = await SaleReceipt.query().upsertGraph({
|
||||
...saleReceiptObj,
|
||||
});
|
||||
// Triggers `onSaleReceiptCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onCreated, {
|
||||
tenantId,
|
||||
saleReceipt,
|
||||
saleReceiptId: saleReceipt.id,
|
||||
trx,
|
||||
} as ISaleReceiptCreatedPayload);
|
||||
|
||||
return saleReceipt;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ISaleReceiptDeletingPayload,
|
||||
ISaleReceiptEventDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
|
||||
@Service()
|
||||
export class DeleteSaleReceipt {
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
/**
|
||||
* Deletes the sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @return {void}
|
||||
*/
|
||||
public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
|
||||
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldSaleReceipt = await SaleReceipt.query()
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validates the sale receipt existance.
|
||||
this.validators.validateReceiptExistance(oldSaleReceipt);
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleReceiptsDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onDeleting, {
|
||||
trx,
|
||||
oldSaleReceipt,
|
||||
tenantId,
|
||||
} as ISaleReceiptDeletingPayload);
|
||||
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_id', saleReceiptId)
|
||||
.where('reference_type', 'SaleReceipt')
|
||||
.delete();
|
||||
|
||||
// Delete the sale receipt transaction.
|
||||
await SaleReceipt.query(trx).where('id', saleReceiptId).delete();
|
||||
|
||||
// Triggers `onSaleReceiptsDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onDeleted, {
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
oldSaleReceipt,
|
||||
trx,
|
||||
} as ISaleReceiptEventDeletedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
119
packages/server/src/services/Sales/Receipts/EditSaleReceipt.ts
Normal file
119
packages/server/src/services/Sales/Receipts/EditSaleReceipt.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import {
|
||||
ISaleReceiptEditedPayload,
|
||||
ISaleReceiptEditingPayload,
|
||||
} from '@/interfaces';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
import { SaleReceiptDTOTransformer } from './SaleReceiptDTOTransformer';
|
||||
|
||||
@Service()
|
||||
export class EditSaleReceipt {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
@Inject()
|
||||
private DTOTransformer: SaleReceiptDTOTransformer;
|
||||
|
||||
/**
|
||||
* Edit details sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @param {ISaleReceipt} saleReceipt
|
||||
* @return {void}
|
||||
*/
|
||||
public async editSaleReceipt(
|
||||
tenantId: number,
|
||||
saleReceiptId: number,
|
||||
saleReceiptDTO: any
|
||||
) {
|
||||
const { SaleReceipt, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve sale receipt or throw not found service error.
|
||||
const oldSaleReceipt = await SaleReceipt.query()
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
// Validates the sale receipt existance.
|
||||
this.validators.validateReceiptExistance(oldSaleReceipt);
|
||||
|
||||
// Retrieves the payment customer model.
|
||||
const paymentCustomer = await Contact.query()
|
||||
.findById(saleReceiptDTO.customerId)
|
||||
.modify('customer')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transform sale receipt DTO to model.
|
||||
const saleReceiptObj = await this.DTOTransformer.transformDTOToModel(
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
paymentCustomer,
|
||||
oldSaleReceipt
|
||||
);
|
||||
// Validate receipt deposit account existance and type.
|
||||
await this.validators.validateReceiptDepositAccountExistance(
|
||||
tenantId,
|
||||
saleReceiptDTO.depositAccountId
|
||||
);
|
||||
// Validate items IDs existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleReceiptDTO.entries
|
||||
);
|
||||
// Validate the sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleReceiptDTO.entries
|
||||
);
|
||||
// Validate sale receipt number uniuqiness.
|
||||
if (saleReceiptDTO.receiptNumber) {
|
||||
await this.validators.validateReceiptNumberUnique(
|
||||
tenantId,
|
||||
saleReceiptDTO.receiptNumber,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
// Edits the sale receipt tranasctions with associated transactions under UOW env.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onSaleReceiptsEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onEditing, {
|
||||
tenantId,
|
||||
oldSaleReceipt,
|
||||
saleReceiptDTO,
|
||||
trx,
|
||||
} as ISaleReceiptEditingPayload);
|
||||
|
||||
// Upsert the receipt graph to the storage.
|
||||
const saleReceipt = await SaleReceipt.query(trx).upsertGraphAndFetch({
|
||||
id: saleReceiptId,
|
||||
...saleReceiptObj,
|
||||
});
|
||||
// Triggers `onSaleReceiptEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.saleReceipt.onEdited, {
|
||||
tenantId,
|
||||
oldSaleReceipt,
|
||||
saleReceipt,
|
||||
saleReceiptId,
|
||||
trx,
|
||||
} as ISaleReceiptEditedPayload);
|
||||
|
||||
return saleReceipt;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { SaleReceiptTransformer } from './SaleReceiptTransformer';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
|
||||
@Service()
|
||||
export class GetSaleReceipt {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
/**
|
||||
* Retrieve sale receipt with associated entries.
|
||||
* @param {Integer} saleReceiptId
|
||||
* @return {ISaleReceipt}
|
||||
*/
|
||||
public async getSaleReceipt(tenantId: number, saleReceiptId: number) {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleReceipt = await SaleReceipt.query()
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('depositAccount')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
// Valdiates the sale receipt existance.
|
||||
this.validators.validateReceiptExistance(saleReceipt);
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
saleReceipt,
|
||||
new SaleReceiptTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISaleReceipt,
|
||||
ISalesReceiptsFilter,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { SaleReceiptTransformer } from './SaleReceiptTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
|
||||
@Service()
|
||||
export class GetSaleReceipts {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
/**
|
||||
* Retrieve sales receipts paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleReceiptFilter} salesReceiptsFilter
|
||||
*/
|
||||
public async getSaleReceipts(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesReceiptsFilter
|
||||
): Promise<{
|
||||
data: ISaleReceipt[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleReceipt,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await SaleReceipt.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('depositAccount');
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformes the estimates models to POJO.
|
||||
const salesEstimates = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new SaleReceiptTransformer()
|
||||
);
|
||||
return {
|
||||
data: salesEstimates,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale invoice list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateSaleReceipt } from './CreateSaleReceipt';
|
||||
import {
|
||||
IFilterMeta,
|
||||
IPaginationMeta,
|
||||
ISaleReceipt,
|
||||
ISalesReceiptsFilter,
|
||||
} from '@/interfaces';
|
||||
import { EditSaleReceipt } from './EditSaleReceipt';
|
||||
import { GetSaleReceipt } from './GetSaleReceipt';
|
||||
import { DeleteSaleReceipt } from './DeleteSaleReceipt';
|
||||
import { GetSaleReceipts } from './GetSaleReceipts';
|
||||
import { CloseSaleReceipt } from './CloseSaleReceipt';
|
||||
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
||||
import { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptApplication {
|
||||
@Inject()
|
||||
private createSaleReceiptService: CreateSaleReceipt;
|
||||
|
||||
@Inject()
|
||||
private editSaleReceiptService: EditSaleReceipt;
|
||||
|
||||
@Inject()
|
||||
private getSaleReceiptService: GetSaleReceipt;
|
||||
|
||||
@Inject()
|
||||
private deleteSaleReceiptService: DeleteSaleReceipt;
|
||||
|
||||
@Inject()
|
||||
private getSaleReceiptsService: GetSaleReceipts;
|
||||
|
||||
@Inject()
|
||||
private closeSaleReceiptService: CloseSaleReceipt;
|
||||
|
||||
@Inject()
|
||||
private getSaleReceiptPdfService: SaleReceiptsPdf;
|
||||
|
||||
@Inject()
|
||||
private saleReceiptNotifyBySmsService: SaleReceiptNotifyBySms;
|
||||
|
||||
/**
|
||||
* Creates a new sale receipt with associated entries.
|
||||
* @param {number} tenantId
|
||||
* @param {} saleReceiptDTO
|
||||
* @returns {Promise<ISaleReceipt>}
|
||||
*/
|
||||
public async createSaleReceipt(
|
||||
tenantId: number,
|
||||
saleReceiptDTO: any
|
||||
): Promise<ISaleReceipt> {
|
||||
return this.createSaleReceiptService.createSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details sale receipt with associated entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @param {} saleReceiptDTO
|
||||
* @returns
|
||||
*/
|
||||
public async editSaleReceipt(
|
||||
tenantId: number,
|
||||
saleReceiptId: number,
|
||||
saleReceiptDTO: any
|
||||
) {
|
||||
return this.editSaleReceiptService.editSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
saleReceiptDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sale receipt with associated entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public async getSaleReceipt(tenantId: number, saleReceiptId: number) {
|
||||
return this.getSaleReceiptService.getSaleReceipt(tenantId, saleReceiptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the sale receipt with associated entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
|
||||
return this.deleteSaleReceiptService.deleteSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sales receipts paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {ISalesReceiptsFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public async getSaleReceipts(
|
||||
tenantId: number,
|
||||
filterDTO: ISalesReceiptsFilter
|
||||
): Promise<{
|
||||
data: ISaleReceipt[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
return this.getSaleReceiptsService.getSaleReceipts(tenantId, filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async closeSaleReceipt(tenantId: number, saleReceiptId: number) {
|
||||
return this.closeSaleReceiptService.closeSaleReceipt(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given sale receipt pdf.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public getSaleReceiptPdf(tenantId: number, saleReceiptId: number) {
|
||||
return this.getSaleReceiptPdfService.saleReceiptPdf(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify receipt customer by SMS of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public saleReceiptNotifyBySms(tenantId: number, saleReceiptId: number) {
|
||||
return this.saleReceiptNotifyBySmsService.notifyBySms(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves sms details of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public getSaleReceiptSmsDetails(tenantId: number, saleReceiptId: number) {
|
||||
return this.saleReceiptNotifyBySmsService.smsDetails(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import { sumBy, omit } from 'lodash';
|
||||
import composeAsync from 'async/compose';
|
||||
import moment from 'moment';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import { SaleReceiptValidators } from './SaleReceiptValidators';
|
||||
import { ICustomer, ISaleReceipt, ISaleReceiptDTO } from '@/interfaces';
|
||||
import { formatDateFields } from '@/utils';
|
||||
import { SaleReceiptIncrement } from './SaleReceiptIncrement';
|
||||
import { ItemEntry } from '@/models';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptDTOTransformer {
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private validators: SaleReceiptValidators;
|
||||
|
||||
@Inject()
|
||||
private receiptIncrement: SaleReceiptIncrement;
|
||||
|
||||
/**
|
||||
* Transform create DTO object to model object.
|
||||
* @param {ISaleReceiptDTO} saleReceiptDTO -
|
||||
* @param {ISaleReceipt} oldSaleReceipt -
|
||||
* @returns {ISaleReceipt}
|
||||
*/
|
||||
async transformDTOToModel(
|
||||
tenantId: number,
|
||||
saleReceiptDTO: ISaleReceiptDTO,
|
||||
paymentCustomer: ICustomer,
|
||||
oldSaleReceipt?: ISaleReceipt
|
||||
): Promise<ISaleReceipt> {
|
||||
const amount = sumBy(saleReceiptDTO.entries, (e) =>
|
||||
ItemEntry.calcAmount(e)
|
||||
);
|
||||
// Retreive the next invoice number.
|
||||
const autoNextNumber = this.receiptIncrement.getNextReceiptNumber(tenantId);
|
||||
|
||||
// Retreive 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 entries = await composeAsync(
|
||||
// Sets default cost and sell account to receipt items entries.
|
||||
this.itemsEntriesService.setItemsEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const initialDTO = {
|
||||
amount,
|
||||
...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
|
||||
'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,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<ISaleReceipt>(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO<ISaleReceipt>(tenantId)
|
||||
)(initialDTO);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
.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];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import AutoIncrementOrdersService from '../AutoIncrementOrdersService';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptIncrement {
|
||||
@Inject()
|
||||
private autoIncrementOrdersService: AutoIncrementOrdersService;
|
||||
|
||||
/**
|
||||
* Retrieve the next unique receipt number.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {string}
|
||||
*/
|
||||
public getNextReceiptNumber(tenantId: number): string {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
tenantId,
|
||||
'sales_receipts'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the receipt next number.
|
||||
* @param {number} tenantId -
|
||||
*/
|
||||
public incrementNextReceiptNumber(tenantId: number) {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
tenantId,
|
||||
'sales_receipts'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ACCOUNT_PARENT_TYPE } from '@/data/AccountTypes';
|
||||
import { ERRORS } from './constants';
|
||||
import { SaleEstimate, SaleReceipt } from '@/models';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptValidators {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validates the sale receipt existance.
|
||||
* @param {SaleEstimate | undefined | null} estimate
|
||||
*/
|
||||
public validateReceiptExistance(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} tenantId - Tenant id.
|
||||
* @param {number} accountId - Account id.
|
||||
*/
|
||||
public async validateReceiptDepositAccountExistance(
|
||||
tenantId: number,
|
||||
accountId: number
|
||||
) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const depositAccount = await accountRepository.findOneById(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 uniquiness on the storage.
|
||||
* @param {number} tenantId -
|
||||
* @param {string} receiptNumber -
|
||||
* @param {number} notReceiptId -
|
||||
*/
|
||||
public async validateReceiptNumberUnique(
|
||||
tenantId: number,
|
||||
receiptNumber: string,
|
||||
notReceiptId?: number
|
||||
) {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleReceipt = await SaleReceipt.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} tenantId
|
||||
* @param {number} customerId - Customer id.
|
||||
*/
|
||||
public async validateCustomerHasNoReceipts(
|
||||
tenantId: number,
|
||||
customerId: number
|
||||
) {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
const receipts = await SaleReceipt.query().where('customer_id', customerId);
|
||||
|
||||
if (receipts.length > 0) {
|
||||
throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_INVOICES);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class SaleReceiptsPdf {
|
||||
export class SaleReceiptsPdf {
|
||||
@Inject()
|
||||
pdfService: PdfService;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class SaleReceiptCostGLEntriesSubscriber {
|
||||
* Writes the receipts cost GL entries once the inventory cost lots be written.
|
||||
* @param {IInventoryCostLotsGLEntriesWriteEvent}
|
||||
*/
|
||||
writeJournalEntriesOnceWriteoffCreate = async ({
|
||||
private writeJournalEntriesOnceWriteoffCreate = async ({
|
||||
trx,
|
||||
startingDate,
|
||||
tenantId,
|
||||
|
||||
Reference in New Issue
Block a user