fix: value property in inventory item details.

This commit is contained in:
a.bouhuolia
2021-06-01 20:33:39 +02:00
parent d47633b8ea
commit 7428a7315a
7 changed files with 105 additions and 41 deletions

View File

@@ -59,11 +59,12 @@ export default class InventoryDetailsController extends BaseController {
* @param {ICashFlowStatement} cashFlow - * @param {ICashFlowStatement} cashFlow -
*/ */
private transformJsonResponse(inventoryDetails) { private transformJsonResponse(inventoryDetails) {
const { data, query } = inventoryDetails; const { data, query, meta } = inventoryDetails;
return { return {
data: this.transfromToResponse(data), 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(), data: inventoryDetailsTable.tableData(),
columns: inventoryDetailsTable.tableColumns(), columns: inventoryDetailsTable.tableColumns(),
}, },
meta: this.transfromToResponse(inventoryDetails.query), query: this.transfromToResponse(inventoryDetails.query),
meta: this.transfromToResponse(inventoryDetails.meta),
}; };
} }

View File

@@ -58,8 +58,9 @@ export interface IInventoryDetailsItemTransaction {
valueMovement: IInventoryDetailsNumber; valueMovement: IInventoryDetailsNumber;
quantity: IInventoryDetailsNumber; quantity: IInventoryDetailsNumber;
value: IInventoryDetailsNumber; total: IInventoryDetailsNumber;
cost: IInventoryDetailsNumber; cost: IInventoryDetailsNumber;
value: IInventoryDetailsNumber;
profitMargin: IInventoryDetailsNumber; profitMargin: IInventoryDetailsNumber;
rate: IInventoryDetailsNumber; rate: IInventoryDetailsNumber;
@@ -74,3 +75,16 @@ export type IInventoryDetailsNode =
| IInventoryDetailsItem | IInventoryDetailsItem
| IInventoryDetailsItemTransaction; | IInventoryDetailsItemTransaction;
export type IInventoryDetailsData = IInventoryDetailsItem[]; export type IInventoryDetailsData = IInventoryDetailsItem[];
export interface IInventoryItemDetailMeta {
isCostComputeRunning: boolean;
organizationName: string;
baseCurrency: string;
}
export interface IInvetoryItemDetailDOO {
data: IInventoryDetailsData;
query: IInventoryDetailsQuery;
meta: IInventoryItemDetailMeta;
}

View File

@@ -1,5 +1,6 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { defaultTo, sumBy, get } from 'lodash'; import { defaultTo, sumBy, get } from 'lodash';
import moment from 'moment';
import { import {
IInventoryDetailsQuery, IInventoryDetailsQuery,
IItem, IItem,
@@ -18,7 +19,6 @@ import {
import FinancialSheet from '../FinancialSheet'; import FinancialSheet from '../FinancialSheet';
import { transformToMapBy, transformToMapKeyValue } from 'utils'; import { transformToMapBy, transformToMapKeyValue } from 'utils';
import { filterDeep } from 'utils/deepdash'; import { filterDeep } from 'utils/deepdash';
import moment from 'moment';
const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' };
@@ -159,7 +159,8 @@ export default class InventoryDetails extends FinancialSheet {
const initial = this.getNumberMeta(0); const initial = this.getNumberMeta(0);
const mapAccumAppender = (a, b) => { 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 totalMeta = this.getNumberMeta(total, { excerptZero: false });
const accum = { ...b, runningValuation: totalMeta }; const accum = { ...b, runningValuation: totalMeta };
@@ -182,19 +183,22 @@ export default class InventoryDetails extends FinancialSheet {
item: IItem, item: IItem,
transaction: IInventoryTransaction transaction: IInventoryTransaction
): IInventoryDetailsItemTransaction { ): IInventoryDetailsItemTransaction {
const value = transaction.quantity * transaction.rate; const total = transaction.quantity * transaction.rate;
const amountMovement = R.curry(this.adjustAmountMovement)( const amountMovement = R.curry(this.adjustAmountMovement)(
transaction.direction transaction.direction
); );
// Quantity movement. // Quantity movement.
const quantityMovement = amountMovement(transaction.quantity); const quantityMovement = amountMovement(transaction.quantity);
const valueMovement = amountMovement(value); const cost = defaultTo(transaction?.costLotAggregated.cost, 0);
// Profit margin. // Profit margin.
const profitMargin = Math.max( const profitMargin = total - cost;
value - transaction.costLotAggregated.cost,
0 // 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 { return {
nodeType: INodeTypes.TRANSACTION, nodeType: INodeTypes.TRANSACTION,
@@ -207,10 +211,11 @@ export default class InventoryDetails extends FinancialSheet {
valueMovement: this.getNumberMeta(valueMovement), valueMovement: this.getNumberMeta(valueMovement),
quantity: this.getNumberMeta(transaction.quantity), quantity: this.getNumberMeta(transaction.quantity),
value: this.getNumberMeta(value), total: this.getNumberMeta(total),
rate: this.getNumberMeta(transaction.rate), rate: this.getNumberMeta(transaction.rate),
cost: this.getNumberMeta(transaction.costLotAggregated.cost), cost: this.getNumberMeta(transaction.costLotAggregated.cost),
value: this.getNumberMeta(value),
profitMargin: this.getNumberMeta(profitMargin), profitMargin: this.getNumberMeta(profitMargin),
@@ -314,14 +319,12 @@ export default class InventoryDetails extends FinancialSheet {
const value = sumBy(transactions, 'valueMovement.number'); const value = sumBy(transactions, 'valueMovement.number');
const quantity = sumBy(transactions, 'quantityMovement.number'); const quantity = sumBy(transactions, 'quantityMovement.number');
const profitMargin = sumBy(transactions, 'profitMargin.number'); const profitMargin = sumBy(transactions, 'profitMargin.number');
const cost = sumBy(transactions, 'cost.number');
return { return {
nodeType: INodeTypes.CLOSING_ENTRY, nodeType: INodeTypes.CLOSING_ENTRY,
date: this.getDateMeta(this.query.toDate), date: this.getDateMeta(this.query.toDate),
quantity: this.getTotalNumberMeta(quantity), quantity: this.getTotalNumberMeta(quantity),
value: this.getTotalNumberMeta(value), value: this.getTotalNumberMeta(value),
cost: this.getTotalNumberMeta(cost),
profitMargin: this.getTotalNumberMeta(profitMargin), profitMargin: this.getTotalNumberMeta(profitMargin),
}; };
} }

View File

@@ -1,7 +1,11 @@
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import { raw } from 'objection'; import { raw } from 'objection';
import moment from 'moment'; import moment from 'moment';
import { IItem, IInventoryDetailsQuery, IInventoryTransaction } from 'interfaces'; import {
IItem,
IInventoryDetailsQuery,
IInventoryTransaction,
} from 'interfaces';
import HasTenancyService from 'services/Tenancy/TenancyService'; import HasTenancyService from 'services/Tenancy/TenancyService';
export default class InventoryDetailsRepository { export default class InventoryDetailsRepository {
@@ -10,7 +14,7 @@ export default class InventoryDetailsRepository {
/** /**
* Retrieve inventory items. * Retrieve inventory items.
* @param {number} tenantId - * @param {number} tenantId -
* @returns {Promise<IItem>} * @returns {Promise<IItem>}
*/ */
public getInventoryItems(tenantId: number): Promise<IItem[]> { public getInventoryItems(tenantId: number): Promise<IItem[]> {
@@ -47,6 +51,7 @@ export default class InventoryDetailsRepository {
raw("IF(`DIRECTION` = 'OUT', `QUANTITY` * `RATE`, 0) as 'VALUE_OUT'") raw("IF(`DIRECTION` = 'OUT', `QUANTITY` * `RATE`, 0) as 'VALUE_OUT'")
) )
.modify('filterDateRange', null, openingBalanceDate) .modify('filterDateRange', null, openingBalanceDate)
.orderBy('date', 'ASC')
.as('inventory_transactions'); .as('inventory_transactions');
const openingBalanceTransactions = await InventoryTransaction.query() const openingBalanceTransactions = await InventoryTransaction.query()
@@ -79,6 +84,7 @@ export default class InventoryDetailsRepository {
const inventoryTransactions = InventoryTransaction.query() const inventoryTransactions = InventoryTransaction.query()
.modify('filterDateRange', filter.fromDate, filter.toDate) .modify('filterDateRange', filter.fromDate, filter.toDate)
.orderBy('date', 'ASC')
.withGraphFetched('meta') .withGraphFetched('meta')
.withGraphFetched('costLotAggregated'); .withGraphFetched('costLotAggregated');

View File

@@ -1,11 +1,16 @@
import moment from 'moment'; import moment from 'moment';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { raw } from 'objection'; import {
import { IInventoryDetailsQuery, IInventoryTransaction } from 'interfaces'; IInventoryDetailsQuery,
IInvetoryItemDetailDOO,
IInventoryItemDetailMeta,
} from 'interfaces';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import InventoryDetails from './InventoryDetails'; import InventoryDetails from './InventoryDetails';
import FinancialSheet from '../FinancialSheet'; import FinancialSheet from '../FinancialSheet';
import InventoryDetailsRepository from './InventoryDetailsRepository'; import InventoryDetailsRepository from './InventoryDetailsRepository';
import InventoryService from 'services/Inventory/Inventory';
import { parseBoolean } from 'utils';
@Service() @Service()
export default class InventoryDetailsService extends FinancialSheet { export default class InventoryDetailsService extends FinancialSheet {
@@ -15,11 +20,14 @@ export default class InventoryDetailsService extends FinancialSheet {
@Inject() @Inject()
reportRepo: InventoryDetailsRepository; reportRepo: InventoryDetailsRepository;
@Inject()
inventoryService: InventoryService;
/** /**
* Defaults balance sheet filter query. * Defaults balance sheet filter query.
* @return {IBalanceSheetQuery} * @return {IBalanceSheetQuery}
*/ */
get defaultQuery(): IInventoryDetailsQuery { private get defaultQuery(): IInventoryDetailsQuery {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('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. * Retrieve the inventory details report data.
* @param {number} tenantId - * @param {number} tenantId -
* @param {IInventoryDetailsQuery} query - * @param {IInventoryDetailsQuery} query -
* @return {Promise<IInvetoryItemDetailDOO>}
*/ */
public async inventoryDetails( public async inventoryDetails(
tenantId: number, tenantId: number,
query: IInventoryDetailsQuery query: IInventoryDetailsQuery
): Promise<any> { ): Promise<IInvetoryItemDetailDOO> {
// Settings tenant service. // Settings tenant service.
const settings = this.tenancy.settings(tenantId); const settings = this.tenancy.settings(tenantId);
const baseCurrency = settings.get({ const baseCurrency = settings.get({
@@ -76,6 +112,8 @@ export default class InventoryDetailsService extends FinancialSheet {
return { return {
data: inventoryDetailsInstance.reportData(), data: inventoryDetailsInstance.reportData(),
query: filter,
meta: this.reportMetadata(tenantId),
}; };
} }
} }

View File

@@ -22,8 +22,8 @@ const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' };
export default class InventoryDetailsTable { export default class InventoryDetailsTable {
/** /**
* Constructor methiod. * Constructor method.
* @param {ICashFlowStatement} reportStatement * @param {ICashFlowStatement} reportStatement - Report statement.
*/ */
constructor(reportStatement) { constructor(reportStatement) {
this.report = reportStatement; this.report = reportStatement;
@@ -59,8 +59,8 @@ export default class InventoryDetailsTable {
accessor: 'quantityMovement.formattedNumber', accessor: 'quantityMovement.formattedNumber',
}, },
{ key: 'rate', accessor: 'rate.formattedNumber' }, { key: 'rate', accessor: 'rate.formattedNumber' },
{ key: 'value_movement', accessor: 'valueMovement.formattedNumber' }, { key: 'total', accessor: 'total.formattedNumber' },
{ key: 'cost', accessor: 'cost.formattedNumber' }, { key: 'value', accessor: 'valueMovement.formattedNumber' },
{ key: 'profit_margin', accessor: 'profitMargin.formattedNumber' }, { key: 'profit_margin', accessor: 'profitMargin.formattedNumber' },
{ key: 'running_quantity', accessor: 'runningQuantity.formattedNumber' }, { key: 'running_quantity', accessor: 'runningQuantity.formattedNumber' },
{ key: 'running_valuation', accessor: 'runningValuation.formattedNumber' }, { key: 'running_valuation', accessor: 'runningValuation.formattedNumber' },
@@ -82,9 +82,9 @@ export default class InventoryDetailsTable {
{ key: 'empty' }, { key: 'empty' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty' }, { key: 'empty' },
{ key: 'empty' },
{ key: 'value', accessor: 'value.formattedNumber' }, { key: 'value', accessor: 'value.formattedNumber' },
]; ];
return tableRowMapper(transaction, columns, { return tableRowMapper(transaction, columns, {
rowTypes: [IROW_TYPE.OPENING_ENTRY], rowTypes: [IROW_TYPE.OPENING_ENTRY],
}); });
@@ -102,8 +102,8 @@ export default class InventoryDetailsTable {
{ key: 'empty' }, { key: 'empty' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty' }, { key: 'empty' },
{ key: 'empty' },
{ key: 'value', accessor: 'value.formattedNumber' }, { key: 'value', accessor: 'value.formattedNumber' },
{ key: 'cost', accessor: 'cost.formattedNumber' },
{ key: 'profitMargin', accessor: 'profitMargin.formattedNumber' }, { key: 'profitMargin', accessor: 'profitMargin.formattedNumber' },
]; ];
@@ -171,13 +171,13 @@ export default class InventoryDetailsTable {
{ key: 'date', label: 'Date' }, { key: 'date', label: 'Date' },
{ key: 'transaction_type', label: 'Transaction type' }, { key: 'transaction_type', label: 'Transaction type' },
{ key: 'transaction_id', label: 'Transaction #' }, { key: 'transaction_id', label: 'Transaction #' },
{ key: 'quantity_movement', label: 'Quantity' }, { key: 'quantity', label: 'Quantity' },
{ key: 'rate', label: 'Rate' }, { key: 'rate', label: 'Rate' },
{ key: 'value_movement', label: 'Value' }, { key: 'total', label: 'Total' },
{ key: 'cost', label: 'Cost' }, { key: 'value', label: 'Value' },
{ key: 'profit_margin', label: 'Profit Margin' }, { key: 'profit_margin', label: 'Profit Margin' },
{ key: 'quantity_on_hand', label: 'Running quantity' }, { key: 'running_quantity', label: 'Running quantity' },
{ key: 'value', label: 'Running Value' }, { key: 'running_value', label: 'Running Value' },
]; ];
} }
} }

View File

@@ -5,6 +5,7 @@ import {
ITransactionsByContactsFilter, ITransactionsByContactsFilter,
IContact, IContact,
ILedger, ILedger,
ILedgerEntry,
} from 'interfaces'; } from 'interfaces';
import FinancialSheet from '../FinancialSheet'; import FinancialSheet from '../FinancialSheet';
@@ -20,20 +21,20 @@ export default class TransactionsByContact extends FinancialSheet {
* @return {Omit<ITransactionsByContactsTransaction, 'runningBalance'>} * @return {Omit<ITransactionsByContactsTransaction, 'runningBalance'>}
*/ */
protected contactTransactionMapper( protected contactTransactionMapper(
transaction entry: ILedgerEntry,
): Omit<ITransactionsByContactsTransaction, 'runningBalance'> { ): Omit<ITransactionsByContactsTransaction, 'runningBalance'> {
const account = this.accountsGraph.getNodeData(transaction.accountId); const account = this.accountsGraph.getNodeData(entry.accountId);
const currencyCode = 'USD'; const currencyCode = 'USD';
return { return {
credit: this.getContactAmount(transaction.credit, currencyCode), credit: this.getContactAmount(entry.credit, currencyCode),
debit: this.getContactAmount(transaction.debit, currencyCode), debit: this.getContactAmount(entry.debit, currencyCode),
accountName: account.name, accountName: account.name,
currencyCode: 'USD', currencyCode: 'USD',
transactionNumber: transaction.transactionNumber, transactionNumber: entry.transactionNumber,
transactionType: transaction.transactionType, transactionType: entry.referenceTypeFormatted,
date: transaction.date, date: entry.date,
createdAt: transaction.createdAt, createdAt: entry.createdAt,
}; };
} }