mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat(reports): inventory valuation report.
feat(reports): sales by items report. feat(reports): purchases by items report.
This commit is contained in:
@@ -98,6 +98,7 @@ export default class JournalCommands {
|
||||
: entry.costAccountId,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
itemQuantity: entry.quantity,
|
||||
});
|
||||
this.journal.debit(debitEntry);
|
||||
});
|
||||
@@ -416,6 +417,7 @@ export default class JournalCommands {
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
itemQuantity: entry.quantity,
|
||||
});
|
||||
this.journal.credit(incomeEntry);
|
||||
});
|
||||
@@ -465,6 +467,8 @@ export default class JournalCommands {
|
||||
account: entry.item.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
itemQuantity: entry.quantity,
|
||||
});
|
||||
this.journal.credit(incomeEntry);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { sumBy, get } from 'lodash';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import {
|
||||
IItem,
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationItem,
|
||||
IAccountTransaction,
|
||||
IInventoryValuationTotal
|
||||
} from 'interfaces';
|
||||
import { transformToMap } from 'utils'
|
||||
|
||||
export default class InventoryValuationSheet extends FinancialSheet {
|
||||
readonly query: IInventoryValuationReportQuery;
|
||||
readonly items: IItem[];
|
||||
readonly INInventoryCostLots: Map<number, IAccountTransaction>;
|
||||
readonly OUTInventoryCostLots: Map<number, IAccountTransaction>;
|
||||
readonly baseCurrency: string;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IInventoryValuationReportQuery} query
|
||||
* @param items
|
||||
* @param INInventoryCostLots
|
||||
* @param OUTInventoryCostLots
|
||||
* @param baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
query: IInventoryValuationReportQuery,
|
||||
items: IItem[],
|
||||
INInventoryCostLots: IAccountTransaction[],
|
||||
OUTInventoryCostLots: IAccountTransaction[],
|
||||
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;
|
||||
}
|
||||
|
||||
getItemTransaction(
|
||||
transactionsMap,
|
||||
itemId: number,
|
||||
): { cost: number, quantity: number } {
|
||||
const meta = transactionsMap.get(itemId);
|
||||
|
||||
const cost = get(meta, 'cost', 0);
|
||||
const quantity = get(meta, 'cost', 0);
|
||||
|
||||
return { cost, quantity };
|
||||
}
|
||||
|
||||
getItemINTransaction(
|
||||
itemId: number,
|
||||
): { cost: number, quantity: number } {
|
||||
return this.getItemTransaction(this.INInventoryCostLots, itemId);
|
||||
}
|
||||
|
||||
getItemOUTTransaction(
|
||||
itemId: number,
|
||||
): { cost: number, quantity: number } {
|
||||
return this.getItemTransaction(this.OUTInventoryCostLots, itemId);
|
||||
}
|
||||
|
||||
getItemValuation(itemId: number): number {
|
||||
const { cost: INValuation } = this.getItemINTransaction(itemId);
|
||||
const { cost: OUTValuation } = this.getItemOUTTransaction(itemId);
|
||||
|
||||
return INValuation - OUTValuation;
|
||||
}
|
||||
|
||||
getItemQuantity(itemId: number): number {
|
||||
const { quantity: INQuantity } = this.getItemINTransaction(itemId);
|
||||
const { quantity: OUTQuantity } = this.getItemOUTTransaction(itemId);
|
||||
|
||||
return INQuantity - OUTQuantity;
|
||||
}
|
||||
|
||||
calcAverage(valuation: number, quantity: number): number {
|
||||
return quantity ? valuation / quantity : 0;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
itemsSection() {
|
||||
return this.items.map(this.itemMapper.bind(this));
|
||||
}
|
||||
|
||||
totalSection(items: IInventoryValuationItem[]): IInventoryValuationTotal {
|
||||
const valuation = sumBy(items, item => item.valuation);
|
||||
const quantity = sumBy(items, item => item.quantity);
|
||||
|
||||
return {
|
||||
valuation,
|
||||
quantity,
|
||||
valuationFormatted: this.formatNumber(valuation),
|
||||
quantityFormatted: this.formatNumber(quantity, { money: false }),
|
||||
};
|
||||
}
|
||||
|
||||
reportData() {
|
||||
const items = this.itemsSection();
|
||||
const total = this.totalSection(items);
|
||||
|
||||
return { items, total };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationSheetMeta,
|
||||
} from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import InventoryValuationSheet from './InventoryValuationSheet';
|
||||
|
||||
@Service()
|
||||
export default class InventoryValuationSheetService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
*/
|
||||
get defaultQuery(): IInventoryValuationReportQuery {
|
||||
return {
|
||||
asDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'always',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
noneTransactions: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the balance sheet meta.
|
||||
* @param {number} tenantId -
|
||||
* @returns {IBalanceSheetMeta}
|
||||
*/
|
||||
reportMetadata(tenantId: number): IInventoryValuationSheetMeta {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const organizationName = settings.get({
|
||||
group: 'organization',
|
||||
key: 'name',
|
||||
});
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
return {
|
||||
organizationName,
|
||||
baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 inventoryItems = await Item.query().where('type', 'inventory');
|
||||
const inventoryItemsIds = inventoryItems.map((item) => item.id);
|
||||
|
||||
// Settings tenant service.
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
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');
|
||||
};
|
||||
|
||||
const INTransactions = await InventoryCostLotTracker.query()
|
||||
.onBuild(commonQuery)
|
||||
.where('direction', 'IN');
|
||||
|
||||
const OUTTransactions = await InventoryCostLotTracker.query()
|
||||
.onBuild(commonQuery)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const inventoryValuationInstance = new InventoryValuationSheet(
|
||||
filter,
|
||||
inventoryItems,
|
||||
INTransactions,
|
||||
OUTTransactions,
|
||||
baseCurrency,
|
||||
);
|
||||
|
||||
const inventoryValuationData = inventoryValuationInstance.reportData();
|
||||
|
||||
return {
|
||||
data: inventoryValuationData,
|
||||
query: filter,
|
||||
meta: this.reportMetadata(tenantId),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { get, sumBy } from 'lodash';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { transformToMap } from 'utils';
|
||||
import {
|
||||
IAccountTransaction,
|
||||
IInventoryValuationTotal,
|
||||
IInventoryValuationItem,
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationStatement,
|
||||
IItem,
|
||||
} from 'interfaces';
|
||||
|
||||
export default class InventoryValuationReport extends FinancialSheet {
|
||||
readonly baseCurrency: string;
|
||||
readonly items: IItem[];
|
||||
readonly itemsTransactions: Map<number, IAccountTransaction>;
|
||||
readonly query: IInventoryValuationReportQuery;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IInventoryValuationReportQuery} query
|
||||
* @param {IItem[]} items
|
||||
* @param {IAccountTransaction[]} itemsTransactions
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
query: IInventoryValuationReportQuery,
|
||||
items: IItem[],
|
||||
itemsTransactions: IAccountTransaction[],
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.items = items;
|
||||
this.itemsTransactions = transformToMap(itemsTransactions, 'itemId');
|
||||
this.query = query;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the item purchase item, cost and average cost price.
|
||||
* @param {number} itemId
|
||||
*/
|
||||
getItemTransaction(
|
||||
itemId: number
|
||||
): { quantity: number; cost: number; average: number } {
|
||||
const transaction = this.itemsTransactions.get(itemId);
|
||||
|
||||
const quantity = get(transaction, 'quantity', 0);
|
||||
const cost = get(transaction, 'cost', 0);
|
||||
|
||||
const average = cost / quantity;
|
||||
|
||||
return { quantity, cost, average };
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping the given item section.
|
||||
* @param {IInventoryValuationItem} item
|
||||
* @returns
|
||||
*/
|
||||
itemSectionMapper(item: IItem): IInventoryValuationItem {
|
||||
const meta = this.getItemTransaction(item.id);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
code: item.code,
|
||||
quantityPurchased: meta.quantity,
|
||||
purchaseCost: meta.cost,
|
||||
averageCostPrice: meta.average,
|
||||
quantityPurchasedFormatted: this.formatNumber(meta.quantity, {
|
||||
money: false,
|
||||
}),
|
||||
purchaseCostFormatted: this.formatNumber(meta.cost),
|
||||
averageCostPriceFormatted: this.formatNumber(meta.average),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the items sections.
|
||||
* @returns {IInventoryValuationItem[]}
|
||||
*/
|
||||
itemsSection(): IInventoryValuationItem[] {
|
||||
return this.items.map(this.itemSectionMapper.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the total section of the sheet.
|
||||
* @param {IInventoryValuationItem[]} items
|
||||
* @returns {IInventoryValuationTotal}
|
||||
*/
|
||||
totalSection(items: IInventoryValuationItem[]): IInventoryValuationTotal {
|
||||
const quantityPurchased = sumBy(items, (item) => item.quantityPurchased);
|
||||
const purchaseCost = sumBy(items, (item) => item.purchaseCost);
|
||||
|
||||
return {
|
||||
quantityPurchased,
|
||||
purchaseCost,
|
||||
quantityPurchasedFormatted: this.formatNumber(quantityPurchased, {
|
||||
money: false,
|
||||
}),
|
||||
purchaseCostFormatted: this.formatNumber(purchaseCost),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sheet data.
|
||||
* @returns
|
||||
*/
|
||||
reportData(): IInventoryValuationStatement {
|
||||
const items = this.itemsSection();
|
||||
const total = this.totalSection(items);
|
||||
|
||||
return { items, total };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
IInventoryValuationReportQuery,
|
||||
IInventoryValuationStatement,
|
||||
IInventoryValuationSheetMeta,
|
||||
} from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import PurchasesByItems from './PurchasesByItems';
|
||||
|
||||
@Service()
|
||||
export default class InventoryValuationReportService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
*/
|
||||
get defaultQuery(): IInventoryValuationReportQuery {
|
||||
return {
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'always',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
noneTransactions: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the balance sheet meta.
|
||||
* @param {number} tenantId -
|
||||
* @returns {IBalanceSheetMeta}
|
||||
*/
|
||||
reportMetadata(tenantId: number): IInventoryValuationSheetMeta {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const organizationName = settings.get({
|
||||
group: 'organization',
|
||||
key: 'name',
|
||||
});
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
return {
|
||||
organizationName,
|
||||
baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve balance sheet statement.
|
||||
* -------------
|
||||
* @param {number} tenantId
|
||||
* @param {IBalanceSheetQuery} query
|
||||
*
|
||||
* @return {IBalanceSheetStatement}
|
||||
*/
|
||||
public async purchasesByItems(
|
||||
tenantId: number,
|
||||
query: IInventoryValuationReportQuery
|
||||
): Promise<{
|
||||
data: IInventoryValuationStatement,
|
||||
query: IInventoryValuationReportQuery,
|
||||
meta: IInventoryValuationSheetMeta,
|
||||
}> {
|
||||
const { Item, InventoryTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
// Settings tenant service.
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
this.logger.info('[purchases_by_items] trying to calculate the report.', {
|
||||
filter,
|
||||
tenantId,
|
||||
});
|
||||
const inventoryItems = await Item.query().where('type', 'inventory');
|
||||
const inventoryItemsIds = inventoryItems.map((item) => item.id);
|
||||
|
||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
||||
const inventoryTransactions = await InventoryTransaction.query().onBuild(
|
||||
(builder: any) => {
|
||||
builder.modify('itemsTotals');
|
||||
builder.modify('INDirection');
|
||||
|
||||
// Filter the inventory items only.
|
||||
builder.whereIn('itemId', inventoryItemsIds);
|
||||
|
||||
// Filter the date range of the sheet.
|
||||
builder.modify('filterDateRange', filter.fromDate, filter.toDate)
|
||||
}
|
||||
);
|
||||
|
||||
const purchasesByItemsInstance = new PurchasesByItems(
|
||||
filter,
|
||||
inventoryItems,
|
||||
inventoryTransactions,
|
||||
baseCurrency
|
||||
);
|
||||
const purchasesByItemsData = purchasesByItemsInstance.reportData();
|
||||
|
||||
return {
|
||||
data: purchasesByItemsData,
|
||||
query: filter,
|
||||
meta: this.reportMetadata(tenantId),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { get, sumBy } from 'lodash';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { transformToMap } from 'utils';
|
||||
import {
|
||||
ISalesByItemsReportQuery,
|
||||
IAccountTransaction,
|
||||
ISalesByItemsItem,
|
||||
ISalesByItemsTotal,
|
||||
ISalesByItemsSheetStatement,
|
||||
IItem,
|
||||
} from 'interfaces';
|
||||
|
||||
export default class SalesByItemsReport extends FinancialSheet {
|
||||
readonly baseCurrency: string;
|
||||
readonly items: IItem[];
|
||||
readonly itemsTransactions: Map<number, IAccountTransaction>;
|
||||
readonly query: ISalesByItemsReportQuery;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {ISalesByItemsReportQuery} query
|
||||
* @param {IItem[]} items
|
||||
* @param {IAccountTransaction[]} itemsTransactions
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
query: ISalesByItemsReportQuery,
|
||||
items: IItem[],
|
||||
itemsTransactions: IAccountTransaction[],
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.items = items;
|
||||
this.itemsTransactions = transformToMap(itemsTransactions, 'itemId');
|
||||
this.query = query;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the item purchase item, cost and average cost price.
|
||||
* @param {number} itemId - Item id.
|
||||
*/
|
||||
getItemTransaction(
|
||||
itemId: number
|
||||
): { quantity: number; cost: number; average: number } {
|
||||
const transaction = this.itemsTransactions.get(itemId);
|
||||
|
||||
const quantity = get(transaction, 'quantity', 0);
|
||||
const cost = get(transaction, 'cost', 0);
|
||||
|
||||
const average = cost / quantity;
|
||||
|
||||
return { quantity, cost, average };
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping the given item section.
|
||||
* @param {ISalesByItemsItem} item
|
||||
* @returns
|
||||
*/
|
||||
itemSectionMapper(item: IItem): ISalesByItemsItem {
|
||||
const meta = this.getItemTransaction(item.id);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
code: item.code,
|
||||
quantitySold: meta.quantity,
|
||||
soldCost: meta.cost,
|
||||
averageSellPrice: meta.average,
|
||||
quantitySoldFormatted: this.formatNumber(meta.quantity, {
|
||||
money: false,
|
||||
}),
|
||||
soldCostFormatted: this.formatNumber(meta.cost),
|
||||
averageSellPriceFormatted: this.formatNumber(meta.average),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the items sections.
|
||||
* @returns {ISalesByItemsItem[]}
|
||||
*/
|
||||
itemsSection(): ISalesByItemsItem[] {
|
||||
return this.items.map(this.itemSectionMapper.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the total section of the sheet.
|
||||
* @param {IInventoryValuationItem[]} items
|
||||
* @returns {IInventoryValuationTotal}
|
||||
*/
|
||||
totalSection(items: ISalesByItemsItem[]): ISalesByItemsTotal {
|
||||
const quantitySold = sumBy(items, (item) => item.quantitySold);
|
||||
const soldCost = sumBy(items, (item) => item.soldCost);
|
||||
|
||||
return {
|
||||
quantitySold,
|
||||
soldCost,
|
||||
quantitySoldFormatted: this.formatNumber(quantitySold, {
|
||||
money: false,
|
||||
}),
|
||||
soldCostFormatted: this.formatNumber(soldCost),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sheet data.
|
||||
* @returns {ISalesByItemsSheetStatement}
|
||||
*/
|
||||
reportData(): ISalesByItemsSheetStatement {
|
||||
const items = this.itemsSection();
|
||||
const total = this.totalSection(items);
|
||||
|
||||
return { items, total };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
ISalesByItemsReportQuery,
|
||||
ISalesByItemsSheetStatement,
|
||||
ISalesByItemsSheetMeta
|
||||
} from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import SalesByItems from './SalesByItems';
|
||||
|
||||
@Service()
|
||||
export default class SalesByItemsReportService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {IBalanceSheetQuery}
|
||||
*/
|
||||
get defaultQuery(): ISalesByItemsReportQuery {
|
||||
return {
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'always',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
noneTransactions: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the balance sheet meta.
|
||||
* @param {number} tenantId -
|
||||
* @returns {IBalanceSheetMeta}
|
||||
*/
|
||||
reportMetadata(tenantId: number): ISalesByItemsSheetMeta {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const organizationName = settings.get({
|
||||
group: 'organization',
|
||||
key: 'name',
|
||||
});
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
return {
|
||||
organizationName,
|
||||
baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve balance sheet statement.
|
||||
* -------------
|
||||
* @param {number} tenantId
|
||||
* @param {IBalanceSheetQuery} query
|
||||
*
|
||||
* @return {IBalanceSheetStatement}
|
||||
*/
|
||||
public async salesByItems(
|
||||
tenantId: number,
|
||||
query: ISalesByItemsReportQuery
|
||||
): Promise<{
|
||||
data: ISalesByItemsSheetStatement,
|
||||
query: ISalesByItemsReportQuery,
|
||||
meta: ISalesByItemsSheetMeta,
|
||||
}> {
|
||||
const { Item, InventoryTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
// Settings tenant service.
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
this.logger.info('[sales_by_items] trying to calculate the report.', {
|
||||
filter,
|
||||
tenantId,
|
||||
});
|
||||
const inventoryItems = await Item.query().where('type', 'inventory');
|
||||
const inventoryItemsIds = inventoryItems.map((item) => item.id);
|
||||
|
||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
||||
const inventoryTransactions = await InventoryTransaction.query().onBuild(
|
||||
(builder: any) => {
|
||||
builder.modify('itemsTotals');
|
||||
builder.modify('OUTDirection');
|
||||
|
||||
// Filter the inventory items only.
|
||||
builder.whereIn('itemId', inventoryItemsIds);
|
||||
|
||||
// Filter the date range of the sheet.
|
||||
builder.modify('filterDateRange', filter.fromDate, filter.toDate)
|
||||
}
|
||||
);
|
||||
|
||||
const purchasesByItemsInstance = new SalesByItems(
|
||||
filter,
|
||||
inventoryItems,
|
||||
inventoryTransactions,
|
||||
baseCurrency
|
||||
);
|
||||
const purchasesByItemsData = purchasesByItemsInstance.reportData();
|
||||
|
||||
return {
|
||||
data: purchasesByItemsData,
|
||||
query: filter,
|
||||
meta: this.reportMetadata(tenantId),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -165,8 +165,6 @@ export default class InventoryAverageCostMethod
|
||||
'transactionId',
|
||||
'transactionType',
|
||||
'createdAt',
|
||||
'sellAccountId',
|
||||
'costAccountId',
|
||||
]),
|
||||
};
|
||||
switch (invTransaction.direction) {
|
||||
|
||||
Reference in New Issue
Block a user