fix: system repositories.

This commit is contained in:
a.bouhuolia
2020-12-17 17:19:16 +02:00
parent 7a847fc895
commit a67b1fbdd0
54 changed files with 1452 additions and 983 deletions

View File

@@ -28,14 +28,13 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import CustomersService from 'services/Contacts/CustomersService';
import SaleEstimateService from 'services/Sales/SalesEstimate';
const ERRORS = {
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
};
/**
@@ -72,13 +71,20 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
saleEstimatesService: SaleEstimateService;
/**
*
*
* Validate whether sale invoice number unqiue on the storage.
*/
async validateInvoiceNumberUnique(tenantId: number, invoiceNumber: string, notInvoiceId?: number) {
async validateInvoiceNumberUnique(
tenantId: number,
invoiceNumber: string,
notInvoiceId?: number
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
this.logger.info('[sale_invoice] validating sale invoice number existance.', { tenantId, invoiceNumber });
this.logger.info(
'[sale_invoice] validating sale invoice number existance.',
{ tenantId, invoiceNumber }
);
const saleInvoice = await SaleInvoice.query()
.findOne('invoice_no', invoiceNumber)
.onBuild((builder) => {
@@ -88,8 +94,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
});
if (saleInvoice) {
this.logger.info('[sale_invoice] sale invoice number not unique.', { tenantId, invoiceNumber });
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE)
this.logger.info('[sale_invoice] sale invoice number not unique.', {
tenantId,
invoiceNumber,
});
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE);
}
}
@@ -116,73 +125,91 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
*/
transformDTOToModel(
tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO|ISaleInvoiceEditDTO,
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
oldSaleInvoice?: ISaleInvoice
): ISaleInvoice {
const { ItemEntry } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
return {
...formatDateFields(
omit(saleInvoiceDTO, ['delivered']),
['invoiceDate', 'dueDate']
),
...formatDateFields(omit(saleInvoiceDTO, ['delivered', 'entries']), [
'invoiceDate',
'dueDate',
]),
// Avoid rewrite the deliver date in edit mode when already published.
...(saleInvoiceDTO.delivered && (!oldSaleInvoice?.deliveredAt)) && ({
deliveredAt: moment().toMySqlDateTime(),
}),
...(saleInvoiceDTO.delivered &&
!oldSaleInvoice?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(),
}),
balance,
paymentAmount: 0,
}
entries: saleInvoiceObj.entries.map((entry) => ({
reference_type: 'SaleInvoice',
...omit(entry, ['amount', 'id']),
})),
};
}
/**
* Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions.
* @async
* @param {number} tenantId =
* @param {ISaleInvoice} saleInvoiceDTO -
* @param {number} tenantId =
* @param {ISaleInvoice} saleInvoiceDTO -
* @return {ISaleInvoice}
*/
public async createSaleInvoice(
tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO
): Promise<ISaleInvoice> {
const { SaleInvoice } = this.tenancy.models(tenantId);
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const invLotNumber = 1;
// Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO);
// Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO.customerId
);
// Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo);
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceDTO.invoiceNo
);
}
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleInvoiceDTO.entries
);
// Validate items should be sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleInvoiceDTO.entries
);
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
const saleInvoice = await SaleInvoice.query()
.insertGraphAndFetch({
...omit(saleInvoiceObj, ['entries']),
entries: saleInvoiceObj.entries.map((entry) => ({
reference_type: 'SaleInvoice',
...omit(entry, ['amount', 'id']),
}))
});
const saleInvoice = await saleInvoiceRepository.upsertGraph({
...saleInvoiceObj,
});
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
tenantId, saleInvoice, saleInvoiceId: saleInvoice.id,
tenantId,
saleInvoice,
saleInvoiceId: saleInvoice.id,
});
this.logger.info('[sale_invoice] successfully inserted.', {
tenantId,
saleInvoice,
});
this.logger.info('[sale_invoice] successfully inserted.', { tenantId, saleInvoice });
return saleInvoice;
}
@@ -190,50 +217,85 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/**
* Edit the given sale invoice.
* @async
* @param {number} tenantId -
* @param {number} tenantId -
* @param {Number} saleInvoiceId -
* @param {ISaleInvoice} saleInvoice -
*/
public async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any): Promise<ISaleInvoice> {
public async editSaleInvoice(
tenantId: number,
saleInvoiceId: number,
saleInvoiceDTO: any
): Promise<ISaleInvoice> {
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId,
saleInvoiceId
);
// Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO, oldSaleInvoice);
const saleInvoiceObj = this.transformDTOToModel(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
);
// Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO.customerId
);
// Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo, saleInvoiceId);
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceDTO.invoiceNo,
saleInvoiceId
);
}
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleInvoiceDTO.entries
);
// Validate non-sellable entries items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleInvoiceDTO.entries
);
// Validate the items entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, saleInvoiceId, 'SaleInvoice', saleInvoiceDTO.entries);
await this.itemsEntriesService.validateEntriesIdsExistance(
tenantId,
saleInvoiceId,
'SaleInvoice',
saleInvoiceDTO.entries
);
this.logger.info('[sale_invoice] trying to update sale invoice.');
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.upsertGraphAndFetch({
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
{
id: saleInvoiceId,
...omit(saleInvoiceObj, ['entries', 'invLotNumber']),
entries: saleInvoiceObj.entries.map((entry) => ({
reference_type: 'SaleInvoice',
...omit(entry, ['amount']),
}))
});
})),
}
);
// Triggers `onSaleInvoiceEdited` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
saleInvoice, oldSaleInvoice, tenantId, saleInvoiceId,
saleInvoice,
oldSaleInvoice,
tenantId,
saleInvoiceId,
});
return saleInvoice;
}
@@ -246,21 +308,27 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
*/
public async deliverSaleInvoice(
tenantId: number,
saleInvoiceId: number,
saleInvoiceId: number
): Promise<void> {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Retrieve details of the given sale invoice id.
const saleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
const saleInvoice = await this.getInvoiceOrThrowError(
tenantId,
saleInvoiceId
);
// Throws error in case the sale invoice already published.
if (saleInvoice.isDelivered) {
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
}
// Record the delivered at on the storage.
await saleInvoiceRepository.update({
deliveredAt: moment().toMySqlDateTime()
}, { id: saleInvoiceId });
await saleInvoiceRepository.update(
{
deliveredAt: moment().toMySqlDateTime(),
},
{ id: saleInvoiceId }
);
}
/**
@@ -269,15 +337,21 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @async
* @param {Number} saleInvoiceId - The given sale invoice id.
*/
public async deleteSaleInvoice(tenantId: number, saleInvoiceId: number): Promise<void> {
public async deleteSaleInvoice(
tenantId: number,
saleInvoiceId: number
): Promise<void> {
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId,
saleInvoiceId
);
// Unlink the converted sale estimates from the given sale invoice.
await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice(
tenantId,
saleInvoiceId,
saleInvoiceId
);
this.logger.info('[sale_invoice] delete sale invoice with entries.');
@@ -288,7 +362,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
.delete();
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, {
tenantId, oldSaleInvoice,
tenantId,
oldSaleInvoice,
});
}
@@ -303,44 +378,48 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
saleInvoice,
saleInvoiceId: number,
override?: boolean
){
) {
this.logger.info('[sale_invoice] saving inventory transactions');
const inventortyTransactions = saleInvoice.entries
.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate',]),
lotNumber: saleInvoice.invLotNumber,
transactionType: 'SaleInvoice',
transactionId: saleInvoiceId,
direction: 'OUT',
date: saleInvoice.invoice_date,
entryId: entry.id,
}));
const inventortyTransactions = saleInvoice.entries.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate']),
lotNumber: saleInvoice.invLotNumber,
transactionType: 'SaleInvoice',
transactionId: saleInvoiceId,
direction: 'OUT',
date: saleInvoice.invoice_date,
entryId: entry.id,
}));
return this.inventoryService.recordInventoryTransactions(
tenantId, inventortyTransactions, override,
tenantId,
inventortyTransactions,
override
);
}
/**
* Deletes the inventory transactions.
* @param {string} transactionType
* @param {number} transactionId
* @param {string} transactionType
* @param {number} transactionId
*/
private async revertInventoryTransactions(tenantId: number, inventoryTransactions: array) {
private async revertInventoryTransactions(
tenantId: number,
inventoryTransactions: array
) {
const { InventoryTransaction } = this.tenancy.models(tenantId);
const opers: Promise<[]>[] = [];
this.logger.info('[sale_invoice] reverting inventory transactions');
inventoryTransactions.forEach((trans: any) => {
switch(trans.direction) {
switch (trans.direction) {
case 'OUT':
if (trans.inventoryTransactionId) {
const revertRemaining = InventoryTransaction.query()
.where('id', trans.inventoryTransactionId)
.where('direction', 'OUT')
.increment('remaining', trans.quanitity);
opers.push(revertRemaining);
}
break;
@@ -361,16 +440,19 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/**
* Retrieve sale invoice with associated entries.
* @async
* @param {Number} saleInvoiceId
* @param {Number} saleInvoiceId
*/
public async getSaleInvoice(tenantId: number, saleInvoiceId: number): Promise<ISaleInvoice> {
public async getSaleInvoice(
tenantId: number,
saleInvoiceId: number
): Promise<ISaleInvoice> {
const { SaleInvoice } = this.tenancy.models(tenantId);
const saleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries')
.withGraphFetched('customer');
if (!saleInvoice) {
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
}
@@ -378,9 +460,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
}
/**
* Schedules compute sale invoice items cost based on each item
* Schedules compute sale invoice items cost based on each item
* cost method.
* @param {ISaleInvoice} saleInvoice
* @param {ISaleInvoice} saleInvoice
* @return {Promise}
*/
async scheduleComputeInvoiceItemsCost(
@@ -396,15 +478,20 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const inventoryItemsIds = chain(saleInvoice.entries)
.filter((entry: IItemEntry) => entry.item.type === 'inventory')
.map((entry: IItemEntry) => entry.itemId)
.uniq().value();
.uniq()
.value();
if (inventoryItemsIds.length === 0) {
await this.writeNonInventoryInvoiceJournals(tenantId, saleInvoice, override);
await this.writeNonInventoryInvoiceJournals(
tenantId,
saleInvoice,
override
);
} else {
await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
saleInvoice.invoice_date,
saleInvoice.invoice_date
);
}
}
@@ -436,40 +523,43 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
await Promise.all([
journal.deleteEntries(),
journal.saveEntries(),
journal.saveBalance(),
journal.saveBalance(),
]);
}
/**
* Retrieve sales invoices filterable and paginated list.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
public async salesInvoicesList(
tenantId: number,
salesInvoicesFilter: ISalesInvoicesFilter
): Promise<{
salesInvoices: ISaleInvoice[],
pagination: IPaginationMeta,
filterMeta: IFilterMeta
salesInvoices: ISaleInvoice[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleInvoice } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleInvoice, salesInvoicesFilter);
this.logger.info('[sale_invoice] try to get sales invoices list.', { tenantId, salesInvoicesFilter });
const {
results,
pagination,
} = await SaleInvoice.query().onBuild((builder) => {
builder.withGraphFetched('entries');
builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder);
}).pagination(
salesInvoicesFilter.page - 1,
salesInvoicesFilter.pageSize,
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleInvoice,
salesInvoicesFilter
);
this.logger.info('[sale_invoice] try to get sales invoices list.', {
tenantId,
salesInvoicesFilter,
});
const { results, pagination } = await SaleInvoice.query()
.onBuild((builder) => {
builder.withGraphFetched('entries');
builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder);
})
.pagination(salesInvoicesFilter.page - 1, salesInvoicesFilter.pageSize);
return {
salesInvoices: results,
pagination,
@@ -479,12 +569,12 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/**
* Retrieve due sales invoices.
* @param {number} tenantId
* @param {number} customerId
* @param {number} tenantId
* @param {number} customerId
*/
public async getPayableInvoices(
tenantId: number,
customerId?: number,
customerId?: number
): Promise<ISaleInvoice> {
const { SaleInvoice } = this.tenancy.models(tenantId);