fix(Journals): sync posting datetime with jorunal entries.

This commit is contained in:
a.bouhuolia
2021-03-29 10:50:44 +02:00
parent 40b2ba099e
commit 9a204282a2
38 changed files with 477 additions and 302 deletions

View File

@@ -678,8 +678,13 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
credit: 0,
referenceId: paymentReceive.id,
referenceType: 'PaymentReceive',
transactionNumber: paymentReceive.paymentReceiveNo,
referenceNumber: paymentReceive.referenceNo,
date: paymentReceive.paymentDate,
userId: authorizedUserId,
createdAt: paymentReceive.createdAt,
};
if (override) {
const transactions = await transactionsRepository.journal({

View File

@@ -1,6 +1,7 @@
import { Service, Inject } from 'typedi';
import { omit, sumBy, join } from 'lodash';
import { omit, sumBy, join, entries } from 'lodash';
import moment from 'moment';
import composeAsync from 'async/compose';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -15,7 +16,7 @@ import {
ISystemUser,
IItem,
IItemEntry,
ISalesInvoicesService
ISalesInvoicesService,
} from 'interfaces';
import JournalPoster from 'services/Accounting/JournalPoster';
import JournalCommands from 'services/Accounting/JournalCommands';
@@ -181,6 +182,30 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
);
}
/**
* Sets the cost/sell accounts to the invoice entries.
*/
setInvoiceEntriesDefaultAccounts(tenantId: number) {
return async (entries: IItemEntry[]) => {
const { Item } = this.tenancy.models(tenantId);
const entriesItemsIds = entries.map((e) => e.itemId);
const items = await Item.query().whereIn('id', entriesItemsIds);
return entries.map((entry) => {
const item = items.find((i) => i.id === entry.itemId);
return {
...entry,
sellAccountId: entry.sellAccountId || item.sellAccountId,
...(item.type === 'inventory' && {
costAccountId: entry.costAccountId || item.costAccountId,
}),
};
});
};
}
/**
* Transformes the create DTO to invoice object model.
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO.
@@ -212,6 +237,16 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
// Validate the invoice is required.
this.validateInvoiceNoRequire(invoiceNo);
const initialEntries = saleInvoiceDTO.entries.map((entry) => ({
referenceType: 'SaleInvoice',
...entry,
}));
const entries = await composeAsync(
// Sets default cost and sell account to invoice items entries.
this.setInvoiceEntriesDefaultAccounts(tenantId)
)(initialEntries);
return {
...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
@@ -227,10 +262,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
// Avoid override payment amount in edit mode.
...(!oldSaleInvoice && { paymentAmount: 0 }),
...(invoiceNo ? { invoiceNo } : {}),
entries: saleInvoiceDTO.entries.map((entry) => ({
referenceType: 'SaleInvoice',
...entry,
})),
entries,
};
}
@@ -259,23 +291,11 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
): Promise<ISaleInvoice> {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Transform DTO object to model object.
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO
);
// Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO.customerId
);
// Validate sale invoice number uniquiness.
if (saleInvoiceObj.invoiceNo) {
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceObj.invoiceNo
);
}
// Validate the from estimate id exists on the storage.
if (saleInvoiceDTO.fromEstimateId) {
const fromEstimate = await this.saleEstimatesService.getSaleEstimateOrThrowError(
@@ -295,11 +315,21 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
tenantId,
saleInvoiceDTO.entries
);
// Transform DTO object to model object.
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO
);
// Validate sale invoice number uniquiness.
if (saleInvoiceObj.invoiceNo) {
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceObj.invoiceNo
);
}
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
const saleInvoice = await saleInvoiceRepository.upsertGraph({
...saleInvoiceObj,
});
const saleInvoice = await saleInvoiceRepository.upsertGraph(saleInvoiceObj);
// Triggers the event `onSaleInvoiceCreated`.
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
tenantId,
@@ -337,25 +367,11 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
tenantId,
saleInvoiceId
);
// Transform DTO object to model object.
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
);
// Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO.customerId
);
// Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceDTO.invoiceNo,
saleInvoiceId
);
}
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
@@ -373,6 +389,20 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
'SaleInvoice',
saleInvoiceDTO.entries
);
// Transform DTO object to model object.
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
);
// Validate sale invoice number uniquiness.
if (saleInvoiceObj.invoiceNo) {
await this.validateInvoiceNumberUnique(
tenantId,
saleInvoiceDTO.invoiceNo,
saleInvoiceId
);
}
// Validate the invoice amount is not smaller than the invoice payment amount.
this.validateInvoiceAmountBiggerPaymentAmount(
saleInvoiceObj.balance,
@@ -445,7 +475,8 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
const { ItemEntry } = this.tenancy.models(tenantId);
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Retrieve the given sale invoice with associated entries or throw not found error.
// Retrieve the given sale invoice with associated entries
// or throw not found error.
const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId,
saleInvoiceId
@@ -497,12 +528,23 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
saleInvoice: ISaleInvoice,
override?: boolean
): Promise<void> {
// Loads the inventory items entries of the given sale invoice.
const inventoryEntries = await this.itemsEntriesService.filterInventoryEntries(
tenantId,
saleInvoice.entries
);
const transaction = {
transactionId: saleInvoice.id,
transactionType: 'SaleInvoice',
date: saleInvoice.invoiceDate,
direction: 'OUT',
entries: inventoryEntries,
createdAt: saleInvoice.created_at,
};
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
tenantId,
saleInvoice.id,
'SaleInvoice',
saleInvoice.invoiceDate,
'OUT',
transaction,
override
);
}

View File

@@ -1,5 +1,5 @@
import { Container, Service, Inject } from 'typedi';
import { chain } from 'lodash';
import { chain, groupBy } from 'lodash';
import moment from 'moment';
import JournalPoster from 'services/Accounting/JournalPoster';
import InventoryService from 'services/Inventory/Inventory';
@@ -108,6 +108,20 @@ export default class SaleInvoicesCost {
});
}
/**
* Grpups by transaction type and id the inventory transactions.
* @param {IInventoryTransaction} invTransactions
* @returns
*/
inventoryTransactionsGroupByType(
invTransactions: { transactionType: string; transactionId: number }[]
): { transactionType: string; transactionId: number }[][] {
return chain(invTransactions)
.groupBy((t) => `${t.transactionType}-${t.transactionId}`)
.values()
.value();
}
/**
* Writes journal entries from sales invoices.
* @param {number} tenantId - The tenant id.
@@ -124,25 +138,28 @@ export default class SaleInvoicesCost {
const inventoryCostLotTrans = await InventoryCostLotTracker.query()
.where('direction', 'OUT')
.modify('groupedEntriesCost')
.modify('filterDateRange', startingDate)
.orderBy('date', 'ASC')
.where('cost', '>', 0)
.withGraphFetched('item');
.withGraphFetched('item')
.withGraphFetched('itemEntry');
const accountsDepGraph = await accountRepository.getDependencyGraph();
const journal = new JournalPoster(tenantId, accountsDepGraph);
const journalCommands = new JournalCommands(journal);
// Groups the inventory cost lots transactions.
const inventoryTransactions = this.inventoryTransactionsGroupByType(
inventoryCostLotTrans
);
if (override) {
await journalCommands.revertInventoryCostJournalEntries(startingDate);
}
inventoryCostLotTrans.forEach(
(inventoryCostLot: IInventoryLotCost & { item: IItem }) => {
journalCommands.saleInvoiceInventoryCost(inventoryCostLot);
}
);
inventoryTransactions.forEach((inventoryLots) => {
journalCommands.saleInvoiceInventoryCost(inventoryLots);
});
return Promise.all([
journal.deleteEntries(),
journal.saveEntries(),