mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
import { sumBy, get, isEmpty } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import {
|
||||
IItem,
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationItem,
|
||||
InventoryCostLotTracker,
|
||||
IInventoryValuationStatement,
|
||||
IInventoryValuationTotal,
|
||||
} from '@/interfaces';
|
||||
import { allPassedConditionsPass, transformToMap } from 'utils';
|
||||
|
||||
export default class InventoryValuationSheet extends FinancialSheet {
|
||||
readonly query: IInventoryValuationReportQuery;
|
||||
readonly items: IItem[];
|
||||
readonly INInventoryCostLots: Map<number, InventoryCostLotTracker>;
|
||||
readonly OUTInventoryCostLots: Map<number, InventoryCostLotTracker>;
|
||||
readonly baseCurrency: string;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IInventoryValuationReportQuery} query
|
||||
* @param items
|
||||
* @param INInventoryCostLots
|
||||
* @param OUTInventoryCostLots
|
||||
* @param baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
query: IInventoryValuationReportQuery,
|
||||
items: IItem[],
|
||||
INInventoryCostLots: Map<number, InventoryCostLotTracker[]>,
|
||||
OUTInventoryCostLots: Map<number, InventoryCostLotTracker[]>,
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.query = query;
|
||||
this.items = items;
|
||||
this.INInventoryCostLots = transformToMap(INInventoryCostLots, 'itemId');
|
||||
this.OUTInventoryCostLots = transformToMap(OUTInventoryCostLots, 'itemId');
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the item cost and quantity from the given transaction map.
|
||||
* @param {Map<number, InventoryCostLotTracker[]>} transactionsMap
|
||||
* @param {number} itemId
|
||||
* @returns
|
||||
*/
|
||||
private getItemTransaction(
|
||||
transactionsMap: Map<number, InventoryCostLotTracker[]>,
|
||||
itemId: number
|
||||
): { cost: number; quantity: number } {
|
||||
const meta = transactionsMap.get(itemId);
|
||||
|
||||
const cost = get(meta, 'cost', 0);
|
||||
const quantity = get(meta, 'quantity', 0);
|
||||
|
||||
return { cost, quantity };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cost and quantity of the givne item from `IN` transactions.
|
||||
* @param {number} itemId -
|
||||
*/
|
||||
private getItemINTransaction(itemId: number): {
|
||||
cost: number;
|
||||
quantity: number;
|
||||
} {
|
||||
return this.getItemTransaction(this.INInventoryCostLots, itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cost and quantity of the given item from `OUT` transactions.
|
||||
* @param {number} itemId -
|
||||
*/
|
||||
private getItemOUTTransaction(itemId: number): {
|
||||
cost: number;
|
||||
quantity: number;
|
||||
} {
|
||||
return this.getItemTransaction(this.OUTInventoryCostLots, itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the item closing valuation.
|
||||
* @param {number} itemId - Item id.
|
||||
*/
|
||||
private getItemValuation(itemId: number): number {
|
||||
const { cost: INValuation } = this.getItemINTransaction(itemId);
|
||||
const { cost: OUTValuation } = this.getItemOUTTransaction(itemId);
|
||||
|
||||
return Math.max(INValuation - OUTValuation, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the item closing quantity.
|
||||
* @param {number} itemId - Item id.
|
||||
*/
|
||||
private getItemQuantity(itemId: number): number {
|
||||
const { quantity: INQuantity } = this.getItemINTransaction(itemId);
|
||||
const { quantity: OUTQuantity } = this.getItemOUTTransaction(itemId);
|
||||
|
||||
return INQuantity - OUTQuantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the item weighted average cost from the given valuation and quantity.
|
||||
* @param {number} valuation
|
||||
* @param {number} quantity
|
||||
* @returns {number}
|
||||
*/
|
||||
private calcAverage(valuation: number, quantity: number): number {
|
||||
return quantity ? valuation / quantity : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping the item model object to inventory valuation item
|
||||
* @param {IItem} item
|
||||
* @returns {IInventoryValuationItem}
|
||||
*/
|
||||
private itemMapper(item: IItem): IInventoryValuationItem {
|
||||
const valuation = this.getItemValuation(item.id);
|
||||
const quantity = this.getItemQuantity(item.id);
|
||||
const average = this.calcAverage(valuation, quantity);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
code: item.code,
|
||||
valuation,
|
||||
quantity,
|
||||
average,
|
||||
valuationFormatted: this.formatNumber(valuation),
|
||||
quantityFormatted: this.formatNumber(quantity, { money: false }),
|
||||
averageFormatted: this.formatNumber(average, { money: false }),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter none transactions items.
|
||||
* @param {IInventoryValuationItem} valuationItem -
|
||||
* @return {boolean}
|
||||
*/
|
||||
private filterNoneTransactions = (
|
||||
valuationItem: IInventoryValuationItem
|
||||
): boolean => {
|
||||
const transactionIN = this.INInventoryCostLots.get(valuationItem.id);
|
||||
const transactionOUT = this.OUTInventoryCostLots.get(valuationItem.id);
|
||||
|
||||
return transactionOUT || transactionIN;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter active only items.
|
||||
* @param {IInventoryValuationItem} valuationItem -
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterActiveOnly = (
|
||||
valuationItem: IInventoryValuationItem
|
||||
): boolean => {
|
||||
return (
|
||||
valuationItem.average !== 0 ||
|
||||
valuationItem.quantity !== 0 ||
|
||||
valuationItem.valuation !== 0
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter none-zero total valuation items.
|
||||
* @param {IInventoryValuationItem} valuationItem
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private filterNoneZero = (valuationItem: IInventoryValuationItem) => {
|
||||
return valuationItem.valuation !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the inventory valuation items based on query.
|
||||
* @param {IInventoryValuationItem} valuationItem
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private itemFilter = (valuationItem: IInventoryValuationItem): boolean => {
|
||||
const { noneTransactions, noneZero, onlyActive } = this.query;
|
||||
|
||||
// Conditions pair filter detarminer.
|
||||
const condsPairFilters = [
|
||||
[noneTransactions, this.filterNoneTransactions],
|
||||
[noneZero, this.filterNoneZero],
|
||||
[onlyActive, this.filterActiveOnly],
|
||||
];
|
||||
return allPassedConditionsPass(condsPairFilters)(valuationItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mappes the items to inventory valuation items nodes.
|
||||
* @param {IItem[]} items
|
||||
* @returns {IInventoryValuationItem[]}
|
||||
*/
|
||||
private itemsMapper = (items: IItem[]): IInventoryValuationItem[] => {
|
||||
return this.items.map(this.itemMapper.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the inventory valuation items nodes.
|
||||
* @param {IInventoryValuationItem[]} nodes -
|
||||
* @returns {IInventoryValuationItem[]}
|
||||
*/
|
||||
private itemsFilter = (
|
||||
nodes: IInventoryValuationItem[]
|
||||
): IInventoryValuationItem[] => {
|
||||
return nodes.filter(this.itemFilter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the items post filter is active.
|
||||
*/
|
||||
private isItemsPostFilter = (): boolean => {
|
||||
return isEmpty(this.query.itemsIds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the inventory valuation items.
|
||||
* @returns {IInventoryValuationItem[]}
|
||||
*/
|
||||
private itemsSection(): IInventoryValuationItem[] {
|
||||
return R.compose(
|
||||
R.when(this.isItemsPostFilter, this.itemsFilter),
|
||||
this.itemsMapper
|
||||
)(this.items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the inventory valuation total.
|
||||
* @param {IInventoryValuationItem[]} items
|
||||
* @returns {IInventoryValuationTotal}
|
||||
*/
|
||||
private totalSection(
|
||||
items: IInventoryValuationItem[]
|
||||
): IInventoryValuationTotal {
|
||||
const valuation = sumBy(items, (item) => item.valuation);
|
||||
const quantity = sumBy(items, (item) => item.quantity);
|
||||
|
||||
return {
|
||||
valuation,
|
||||
quantity,
|
||||
valuationFormatted: this.formatTotalNumber(valuation),
|
||||
quantityFormatted: this.formatTotalNumber(quantity, { money: false }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the inventory valuation report data.
|
||||
* @returns {IInventoryValuationStatement}
|
||||
*/
|
||||
public reportData(): IInventoryValuationStatement {
|
||||
const items = this.itemsSection();
|
||||
const total = this.totalSection(items);
|
||||
|
||||
return items.length > 0 ? { items, total } : {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationSheetMeta,
|
||||
} from '@/interfaces';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import InventoryValuationSheet from './InventoryValuationSheet';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class InventoryValuationSheetService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
inventoryService: InventoryService;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
*/
|
||||
get defaultQuery(): IInventoryValuationReportQuery {
|
||||
return {
|
||||
asDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
itemsIds: [],
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'always',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
noneTransactions: true,
|
||||
noneZero: false,
|
||||
onlyActive: false,
|
||||
|
||||
warehousesIds: [],
|
||||
branchesIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the balance sheet meta.
|
||||
* @param {number} tenantId -
|
||||
* @returns {IBalanceSheetMeta}
|
||||
*/
|
||||
reportMetadata(tenantId: number): IInventoryValuationSheetMeta {
|
||||
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 {
|
||||
organizationName,
|
||||
baseCurrency,
|
||||
isCostComputeRunning,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Inventory valuation sheet.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IInventoryValuationReportQuery} query - Valuation query.
|
||||
*/
|
||||
public async inventoryValuationSheet(
|
||||
tenantId: number,
|
||||
query: IInventoryValuationReportQuery
|
||||
) {
|
||||
const { Item, InventoryCostLotTracker } = this.tenancy.models(tenantId);
|
||||
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
const inventoryItems = await Item.query().onBuild((q) => {
|
||||
q.where('type', 'inventory');
|
||||
|
||||
if (filter.itemsIds.length > 0) {
|
||||
q.whereIn('id', filter.itemsIds);
|
||||
}
|
||||
});
|
||||
const inventoryItemsIds = inventoryItems.map((item) => item.id);
|
||||
|
||||
const commonQuery = (builder) => {
|
||||
builder.whereIn('item_id', inventoryItemsIds);
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.sum('cost as cost');
|
||||
builder.select('itemId');
|
||||
builder.groupBy('itemId');
|
||||
|
||||
if (!isEmpty(query.branchesIds)) {
|
||||
builder.modify('filterByBranches', query.branchesIds);
|
||||
}
|
||||
if (!isEmpty(query.warehousesIds)) {
|
||||
builder.modify('filterByWarehouses', query.warehousesIds);
|
||||
}
|
||||
};
|
||||
// Retrieve the inventory cost `IN` transactions.
|
||||
const INTransactions = await InventoryCostLotTracker.query()
|
||||
.onBuild(commonQuery)
|
||||
.where('direction', 'IN');
|
||||
|
||||
// Retrieve the inventory cost `OUT` transactions.
|
||||
const OUTTransactions = await InventoryCostLotTracker.query()
|
||||
.onBuild(commonQuery)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const inventoryValuationInstance = new InventoryValuationSheet(
|
||||
filter,
|
||||
inventoryItems,
|
||||
INTransactions,
|
||||
OUTTransactions,
|
||||
tenant.metadata.baseCurrency
|
||||
);
|
||||
// Retrieve the inventory valuation report data.
|
||||
const inventoryValuationData = inventoryValuationInstance.reportData();
|
||||
|
||||
return {
|
||||
data: inventoryValuationData,
|
||||
query: filter,
|
||||
meta: this.reportMetadata(tenantId),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user