From 7428a7315af26a3c693fe6d0ef1476dcf2e89d61 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Tue, 1 Jun 2021 20:33:39 +0200 Subject: [PATCH] fix: `value` property in inventory item details. --- .../InventoryDetails/index.ts | 8 ++-- server/src/interfaces/InventoryDetails.ts | 16 ++++++- .../InventoryDetails/InventoryDetails.ts | 27 ++++++----- .../InventoryDetailsRepository.ts | 10 +++- .../InventoryDetailsService.ts | 46 +++++++++++++++++-- .../InventoryDetails/InventoryDetailsTable.ts | 22 ++++----- .../TransactionsByContact.ts | 17 +++---- 7 files changed, 105 insertions(+), 41 deletions(-) diff --git a/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts b/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts index 74f30ded9..fdb117eea 100644 --- a/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts +++ b/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts @@ -59,11 +59,12 @@ export default class InventoryDetailsController extends BaseController { * @param {ICashFlowStatement} cashFlow - */ private transformJsonResponse(inventoryDetails) { - const { data, query } = inventoryDetails; + const { data, query, meta } = inventoryDetails; return { data: this.transfromToResponse(data), - meta: this.transfromToResponse(query), + query: this.transfromToResponse(query), + meta: this.transfromToResponse(meta), }; } @@ -78,7 +79,8 @@ export default class InventoryDetailsController extends BaseController { data: inventoryDetailsTable.tableData(), columns: inventoryDetailsTable.tableColumns(), }, - meta: this.transfromToResponse(inventoryDetails.query), + query: this.transfromToResponse(inventoryDetails.query), + meta: this.transfromToResponse(inventoryDetails.meta), }; } diff --git a/server/src/interfaces/InventoryDetails.ts b/server/src/interfaces/InventoryDetails.ts index 5ce72b343..69680f04f 100644 --- a/server/src/interfaces/InventoryDetails.ts +++ b/server/src/interfaces/InventoryDetails.ts @@ -58,8 +58,9 @@ export interface IInventoryDetailsItemTransaction { valueMovement: IInventoryDetailsNumber; quantity: IInventoryDetailsNumber; - value: IInventoryDetailsNumber; + total: IInventoryDetailsNumber; cost: IInventoryDetailsNumber; + value: IInventoryDetailsNumber; profitMargin: IInventoryDetailsNumber; rate: IInventoryDetailsNumber; @@ -74,3 +75,16 @@ export type IInventoryDetailsNode = | IInventoryDetailsItem | IInventoryDetailsItemTransaction; export type IInventoryDetailsData = IInventoryDetailsItem[]; + + +export interface IInventoryItemDetailMeta { + isCostComputeRunning: boolean; + organizationName: string; + baseCurrency: string; +} + +export interface IInvetoryItemDetailDOO { + data: IInventoryDetailsData; + query: IInventoryDetailsQuery; + meta: IInventoryItemDetailMeta; +} \ No newline at end of file diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts index c94ec6e94..1bdbadc41 100644 --- a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; import { defaultTo, sumBy, get } from 'lodash'; +import moment from 'moment'; import { IInventoryDetailsQuery, IItem, @@ -18,7 +19,6 @@ import { import FinancialSheet from '../FinancialSheet'; import { transformToMapBy, transformToMapKeyValue } from 'utils'; import { filterDeep } from 'utils/deepdash'; -import moment from 'moment'; const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; @@ -159,7 +159,8 @@ export default class InventoryDetails extends FinancialSheet { const initial = this.getNumberMeta(0); const mapAccumAppender = (a, b) => { - const total = a.runningValuation.number + b.valueMovement.number; + const adjusmtent = b.direction === 'OUT' ? -1 : 1; + const total = a.runningValuation.number + b.cost.number * adjusmtent; const totalMeta = this.getNumberMeta(total, { excerptZero: false }); const accum = { ...b, runningValuation: totalMeta }; @@ -182,19 +183,22 @@ export default class InventoryDetails extends FinancialSheet { item: IItem, transaction: IInventoryTransaction ): IInventoryDetailsItemTransaction { - const value = transaction.quantity * transaction.rate; + const total = transaction.quantity * transaction.rate; const amountMovement = R.curry(this.adjustAmountMovement)( transaction.direction ); // Quantity movement. const quantityMovement = amountMovement(transaction.quantity); - const valueMovement = amountMovement(value); + const cost = defaultTo(transaction?.costLotAggregated.cost, 0); - // Profit margin. - const profitMargin = Math.max( - value - transaction.costLotAggregated.cost, - 0 - ); + // Profit margin. + const profitMargin = total - cost; + + // Value from computed cost in `OUT` or from total sell price in `IN` transaction. + const value = transaction.direction === 'OUT' ? cost : total; + + // Value movement depends on transaction direction. + const valueMovement = amountMovement(value); return { nodeType: INodeTypes.TRANSACTION, @@ -207,10 +211,11 @@ export default class InventoryDetails extends FinancialSheet { valueMovement: this.getNumberMeta(valueMovement), quantity: this.getNumberMeta(transaction.quantity), - value: this.getNumberMeta(value), + total: this.getNumberMeta(total), rate: this.getNumberMeta(transaction.rate), cost: this.getNumberMeta(transaction.costLotAggregated.cost), + value: this.getNumberMeta(value), profitMargin: this.getNumberMeta(profitMargin), @@ -314,14 +319,12 @@ export default class InventoryDetails extends FinancialSheet { const value = sumBy(transactions, 'valueMovement.number'); const quantity = sumBy(transactions, 'quantityMovement.number'); const profitMargin = sumBy(transactions, 'profitMargin.number'); - const cost = sumBy(transactions, 'cost.number'); return { nodeType: INodeTypes.CLOSING_ENTRY, date: this.getDateMeta(this.query.toDate), quantity: this.getTotalNumberMeta(quantity), value: this.getTotalNumberMeta(value), - cost: this.getTotalNumberMeta(cost), profitMargin: this.getTotalNumberMeta(profitMargin), }; } diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts index c8d0abe87..8cc98ca0a 100644 --- a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts @@ -1,7 +1,11 @@ import { Inject } from 'typedi'; import { raw } from 'objection'; import moment from 'moment'; -import { IItem, IInventoryDetailsQuery, IInventoryTransaction } from 'interfaces'; +import { + IItem, + IInventoryDetailsQuery, + IInventoryTransaction, +} from 'interfaces'; import HasTenancyService from 'services/Tenancy/TenancyService'; export default class InventoryDetailsRepository { @@ -10,7 +14,7 @@ export default class InventoryDetailsRepository { /** * Retrieve inventory items. - * @param {number} tenantId - + * @param {number} tenantId - * @returns {Promise} */ public getInventoryItems(tenantId: number): Promise { @@ -47,6 +51,7 @@ export default class InventoryDetailsRepository { raw("IF(`DIRECTION` = 'OUT', `QUANTITY` * `RATE`, 0) as 'VALUE_OUT'") ) .modify('filterDateRange', null, openingBalanceDate) + .orderBy('date', 'ASC') .as('inventory_transactions'); const openingBalanceTransactions = await InventoryTransaction.query() @@ -79,6 +84,7 @@ export default class InventoryDetailsRepository { const inventoryTransactions = InventoryTransaction.query() .modify('filterDateRange', filter.fromDate, filter.toDate) + .orderBy('date', 'ASC') .withGraphFetched('meta') .withGraphFetched('costLotAggregated'); diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts index 186d5d29e..c1628042e 100644 --- a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts @@ -1,11 +1,16 @@ import moment from 'moment'; import { Service, Inject } from 'typedi'; -import { raw } from 'objection'; -import { IInventoryDetailsQuery, IInventoryTransaction } from 'interfaces'; +import { + IInventoryDetailsQuery, + IInvetoryItemDetailDOO, + IInventoryItemDetailMeta, +} from 'interfaces'; import TenancyService from 'services/Tenancy/TenancyService'; import InventoryDetails from './InventoryDetails'; import FinancialSheet from '../FinancialSheet'; import InventoryDetailsRepository from './InventoryDetailsRepository'; +import InventoryService from 'services/Inventory/Inventory'; +import { parseBoolean } from 'utils'; @Service() export default class InventoryDetailsService extends FinancialSheet { @@ -15,11 +20,14 @@ export default class InventoryDetailsService extends FinancialSheet { @Inject() reportRepo: InventoryDetailsRepository; + @Inject() + inventoryService: InventoryService; + /** * Defaults balance sheet filter query. * @return {IBalanceSheetQuery} */ - get defaultQuery(): IInventoryDetailsQuery { + private get defaultQuery(): IInventoryDetailsQuery { return { fromDate: moment().startOf('year').format('YYYY-MM-DD'), toDate: moment().endOf('year').format('YYYY-MM-DD'), @@ -34,15 +42,43 @@ export default class InventoryDetailsService extends FinancialSheet { }; } + /** + * Retrieve the balance sheet meta. + * @param {number} tenantId - + * @returns {IInventoryItemDetailMeta} + */ + private reportMetadata(tenantId: number): IInventoryItemDetailMeta { + const settings = this.tenancy.settings(tenantId); + + const isCostComputeRunning = + this.inventoryService.isItemsCostComputeRunning(tenantId); + + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + return { + isCostComputeRunning: parseBoolean(isCostComputeRunning, false), + organizationName, + baseCurrency, + }; + } + /** * Retrieve the inventory details report data. * @param {number} tenantId - * @param {IInventoryDetailsQuery} query - + * @return {Promise} */ public async inventoryDetails( tenantId: number, query: IInventoryDetailsQuery - ): Promise { + ): Promise { // Settings tenant service. const settings = this.tenancy.settings(tenantId); const baseCurrency = settings.get({ @@ -76,6 +112,8 @@ export default class InventoryDetailsService extends FinancialSheet { return { data: inventoryDetailsInstance.reportData(), + query: filter, + meta: this.reportMetadata(tenantId), }; } } diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts index 9af965aad..8b0923bfd 100644 --- a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts @@ -22,8 +22,8 @@ const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; export default class InventoryDetailsTable { /** - * Constructor methiod. - * @param {ICashFlowStatement} reportStatement + * Constructor method. + * @param {ICashFlowStatement} reportStatement - Report statement. */ constructor(reportStatement) { this.report = reportStatement; @@ -59,8 +59,8 @@ export default class InventoryDetailsTable { accessor: 'quantityMovement.formattedNumber', }, { key: 'rate', accessor: 'rate.formattedNumber' }, - { key: 'value_movement', accessor: 'valueMovement.formattedNumber' }, - { key: 'cost', accessor: 'cost.formattedNumber' }, + { key: 'total', accessor: 'total.formattedNumber' }, + { key: 'value', accessor: 'valueMovement.formattedNumber' }, { key: 'profit_margin', accessor: 'profitMargin.formattedNumber' }, { key: 'running_quantity', accessor: 'runningQuantity.formattedNumber' }, { key: 'running_valuation', accessor: 'runningValuation.formattedNumber' }, @@ -82,9 +82,9 @@ export default class InventoryDetailsTable { { key: 'empty' }, { key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'empty' }, + { key: 'empty' }, { key: 'value', accessor: 'value.formattedNumber' }, ]; - return tableRowMapper(transaction, columns, { rowTypes: [IROW_TYPE.OPENING_ENTRY], }); @@ -102,8 +102,8 @@ export default class InventoryDetailsTable { { key: 'empty' }, { key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'empty' }, + { key: 'empty' }, { key: 'value', accessor: 'value.formattedNumber' }, - { key: 'cost', accessor: 'cost.formattedNumber' }, { key: 'profitMargin', accessor: 'profitMargin.formattedNumber' }, ]; @@ -171,13 +171,13 @@ export default class InventoryDetailsTable { { key: 'date', label: 'Date' }, { key: 'transaction_type', label: 'Transaction type' }, { key: 'transaction_id', label: 'Transaction #' }, - { key: 'quantity_movement', label: 'Quantity' }, + { key: 'quantity', label: 'Quantity' }, { key: 'rate', label: 'Rate' }, - { key: 'value_movement', label: 'Value' }, - { key: 'cost', label: 'Cost' }, + { key: 'total', label: 'Total' }, + { key: 'value', label: 'Value' }, { key: 'profit_margin', label: 'Profit Margin' }, - { key: 'quantity_on_hand', label: 'Running quantity' }, - { key: 'value', label: 'Running Value' }, + { key: 'running_quantity', label: 'Running quantity' }, + { key: 'running_value', label: 'Running Value' }, ]; } } diff --git a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts index b10820f32..a3ff121a9 100644 --- a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts +++ b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts @@ -5,6 +5,7 @@ import { ITransactionsByContactsFilter, IContact, ILedger, + ILedgerEntry, } from 'interfaces'; import FinancialSheet from '../FinancialSheet'; @@ -20,20 +21,20 @@ export default class TransactionsByContact extends FinancialSheet { * @return {Omit} */ protected contactTransactionMapper( - transaction + entry: ILedgerEntry, ): Omit { - const account = this.accountsGraph.getNodeData(transaction.accountId); + const account = this.accountsGraph.getNodeData(entry.accountId); const currencyCode = 'USD'; return { - credit: this.getContactAmount(transaction.credit, currencyCode), - debit: this.getContactAmount(transaction.debit, currencyCode), + credit: this.getContactAmount(entry.credit, currencyCode), + debit: this.getContactAmount(entry.debit, currencyCode), accountName: account.name, currencyCode: 'USD', - transactionNumber: transaction.transactionNumber, - transactionType: transaction.transactionType, - date: transaction.date, - createdAt: transaction.createdAt, + transactionNumber: entry.transactionNumber, + transactionType: entry.referenceTypeFormatted, + date: entry.date, + createdAt: entry.createdAt, }; }