2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 01:14:03 +00:00
books/reports/inventory/StockLedger.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

369 lines
9.0 KiB
TypeScript
Raw Normal View History

2022-11-09 18:42:00 +00:00
import { Fyo, t } from 'fyo';
2022-11-25 09:21:58 +00:00
import { RawValueMap } from 'fyo/core/types';
2022-11-03 14:55:08 +00:00
import { Action } from 'fyo/model/types';
2022-11-09 18:42:00 +00:00
import { cloneDeep } from 'lodash';
import { DateTime } from 'luxon';
import { ValuationMethod } from 'models/inventory/types';
2022-11-03 14:55:08 +00:00
import { ModelNameEnum } from 'models/types';
import getCommonExportActions from 'reports/commonExporter';
import { Report } from 'reports/Report';
import { ColumnField, ReportCell, ReportData, ReportRow } from 'reports/types';
2022-11-25 09:21:58 +00:00
import { Field, RawValue } from 'schemas/types';
2022-11-03 14:55:08 +00:00
import { isNumeric } from 'src/utils';
2022-11-09 18:42:00 +00:00
import { getRawStockLedgerEntries, getStockLedgerEntries } from './helpers';
2022-11-25 09:21:58 +00:00
import { ComputedStockLedgerEntry, ReferenceType } from './types';
2022-11-03 14:55:08 +00:00
export class StockLedger extends Report {
static title = t`Stock Ledger`;
static reportName = 'stock-ledger';
2022-11-21 07:26:13 +00:00
static isInventory = true;
2022-11-09 18:42:00 +00:00
usePagination: boolean = true;
2022-11-03 14:55:08 +00:00
2022-11-09 18:42:00 +00:00
_rawData?: ComputedStockLedgerEntry[];
2022-11-03 14:55:08 +00:00
loading: boolean = false;
2022-11-09 18:42:00 +00:00
shouldRefresh: boolean = false;
item?: string;
location?: string;
fromDate?: string;
toDate?: string;
ascending?: boolean;
referenceType?: ReferenceType = 'All';
referenceName?: string;
2022-11-09 18:42:00 +00:00
groupBy: 'none' | 'item' | 'location' = 'none';
constructor(fyo: Fyo) {
super(fyo);
this._setObservers();
}
async setDefaultFilters() {
if (!this.toDate) {
this.toDate = DateTime.now().plus({ days: 1 }).toISODate();
this.fromDate = DateTime.now().minus({ years: 1 }).toISODate();
}
}
2022-11-03 14:55:08 +00:00
async setReportData(
filter?: string | undefined,
force?: boolean | undefined
): Promise<void> {
this.loading = true;
2022-11-09 18:42:00 +00:00
this.reportData = await this._getReportData(force);
2022-11-03 14:55:08 +00:00
this.loading = false;
}
2022-11-09 18:42:00 +00:00
async _getReportData(force?: boolean): Promise<ReportData> {
if (this.shouldRefresh || force || !this._rawData?.length) {
await this._setRawData();
}
const rawData = cloneDeep(this._rawData);
if (!rawData) {
return [];
}
const filtered = this._getFilteredRawData(rawData);
const grouped = this._getGroupedRawData(filtered);
2022-11-25 09:21:58 +00:00
return grouped.map((row) =>
this._convertRawDataRowToReportRow(row as RawValueMap, {
quantity: null,
valueChange: null,
})
);
2022-11-09 18:42:00 +00:00
}
async _setRawData() {
const valuationMethod =
(this.fyo.singles.InventorySettings?.valuationMethod as
| ValuationMethod
| undefined) ?? ValuationMethod.FIFO;
2022-11-09 18:42:00 +00:00
const rawSLEs = await getRawStockLedgerEntries(this.fyo);
this._rawData = getStockLedgerEntries(rawSLEs, valuationMethod);
2022-11-09 18:42:00 +00:00
}
_getFilteredRawData(rawData: ComputedStockLedgerEntry[]) {
const filteredRawData: ComputedStockLedgerEntry[] = [];
if (!rawData.length) {
return [];
}
2022-11-25 09:21:58 +00:00
const fromDate = this.fromDate ? Date.parse(this.fromDate) : null;
const toDate = this.toDate ? Date.parse(this.toDate) : null;
2022-11-09 18:42:00 +00:00
if (!this.ascending) {
rawData.reverse();
}
let i = 0;
for (const idx in rawData) {
const row = rawData[idx];
if (this.item && row.item !== this.item) {
continue;
}
if (this.location && row.location !== this.location) {
continue;
}
2022-11-25 09:21:58 +00:00
const date = row.date.valueOf();
if (toDate && date > toDate) {
2022-11-09 18:42:00 +00:00
continue;
}
2022-11-25 09:21:58 +00:00
if (fromDate && date < fromDate) {
2022-11-09 18:42:00 +00:00
continue;
2022-11-03 14:55:08 +00:00
}
if (
this.referenceType !== 'All' &&
row.referenceType !== this.referenceType
) {
continue;
}
if (this.referenceName && row.referenceName !== this.referenceName) {
continue;
}
2022-11-09 18:42:00 +00:00
row.name = ++i;
filteredRawData.push(row);
}
return filteredRawData;
2022-11-03 14:55:08 +00:00
}
2022-11-09 18:42:00 +00:00
_getGroupedRawData(rawData: ComputedStockLedgerEntry[]) {
const groupBy = this.groupBy;
if (groupBy === 'none') {
return rawData;
}
const groups: Map<string, ComputedStockLedgerEntry[]> = new Map();
2022-11-03 14:55:08 +00:00
for (const row of rawData) {
2022-11-09 18:42:00 +00:00
const key = row[groupBy];
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)?.push(row);
2022-11-03 14:55:08 +00:00
}
2022-11-09 18:42:00 +00:00
const groupedRawData: (ComputedStockLedgerEntry | { name: null })[] = [];
let i = 0;
for (const key of groups.keys()) {
for (const row of groups.get(key) ?? []) {
row.name = ++i;
groupedRawData.push(row);
}
groupedRawData.push({ name: null });
}
if (groupedRawData.at(-1)?.name === null) {
groupedRawData.pop();
}
return groupedRawData;
2022-11-03 14:55:08 +00:00
}
2022-11-09 18:42:00 +00:00
_convertRawDataRowToReportRow(
2022-11-25 09:21:58 +00:00
row: RawValueMap,
colouredMap: Record<string, 'red' | 'green' | null>
2022-11-09 18:42:00 +00:00
): ReportRow {
2022-11-03 14:55:08 +00:00
const cells: ReportCell[] = [];
2022-11-09 18:42:00 +00:00
const columns = this.getColumns();
if (row.name === null) {
return {
isEmpty: true,
cells: columns.map((c) => ({
rawValue: '',
value: '',
width: c.width ?? 1,
})),
};
}
for (const col of columns) {
const fieldname = col.fieldname as keyof ComputedStockLedgerEntry;
const fieldtype = col.fieldtype;
2022-11-25 09:21:58 +00:00
const rawValue = row[fieldname] as RawValue;
2022-11-03 14:55:08 +00:00
const value = this.fyo.format(rawValue, fieldtype);
const align = isNumeric(fieldtype) ? 'right' : 'left';
2022-11-25 09:21:58 +00:00
const isColoured = fieldname in colouredMap;
const isNumber = typeof rawValue === 'number';
2022-11-09 18:42:00 +00:00
let color: 'red' | 'green' | undefined = undefined;
2022-11-25 09:21:58 +00:00
if (isColoured && colouredMap[fieldname]) {
color = colouredMap[fieldname]!;
} else if (isColoured && isNumber && rawValue > 0) {
2022-11-09 18:42:00 +00:00
color = 'green';
2022-11-25 09:21:58 +00:00
} else if (isColoured && isNumber && rawValue < 0) {
2022-11-09 18:42:00 +00:00
color = 'red';
}
cells.push({ rawValue, value, align, color, width: col.width });
2022-11-03 14:55:08 +00:00
}
return { cells };
}
2022-11-09 18:42:00 +00:00
_setObservers() {
const listener = () => (this.shouldRefresh = true);
this.fyo.doc.observer.on(
`sync:${ModelNameEnum.StockLedgerEntry}`,
listener
);
this.fyo.doc.observer.on(
`delete:${ModelNameEnum.StockLedgerEntry}`,
listener
);
}
2022-11-03 14:55:08 +00:00
getColumns(): ColumnField[] {
2022-11-09 18:42:00 +00:00
return [
{
fieldname: 'name',
label: '#',
fieldtype: 'Int',
width: 0.5,
},
{
fieldname: 'date',
label: 'Date',
fieldtype: 'Datetime',
width: 1.25,
},
{
fieldname: 'item',
label: 'Item',
fieldtype: 'Link',
},
{
fieldname: 'location',
label: 'Location',
fieldtype: 'Link',
},
{
fieldname: 'quantity',
label: 'Quantity',
fieldtype: 'Float',
},
{
fieldname: 'balanceQuantity',
label: 'Balance Qty.',
fieldtype: 'Float',
},
{
fieldname: 'incomingRate',
label: 'Incoming rate',
fieldtype: 'Currency',
},
{
fieldname: 'valuationRate',
2022-11-25 09:21:58 +00:00
label: 'Valuation Rate',
2022-11-09 18:42:00 +00:00
fieldtype: 'Currency',
},
{
fieldname: 'balanceValue',
label: 'Balance Value',
fieldtype: 'Currency',
},
{
fieldname: 'valueChange',
label: 'Value Change',
fieldtype: 'Currency',
},
{
fieldname: 'referenceName',
label: 'Ref. Name',
fieldtype: 'DynamicLink',
},
{
fieldname: 'referenceType',
label: 'Ref. Type',
fieldtype: 'Data',
},
];
2022-11-03 14:55:08 +00:00
}
2022-11-09 18:42:00 +00:00
getFilters(): Field[] {
return [
{
fieldtype: 'Select',
options: [
{ label: t`All`, value: 'All' },
{ label: t`Stock Movements`, value: 'StockMovement' },
{ label: t`Shipment`, value: 'Shipment' },
{ label: t`Purchase Receipt`, value: 'PurchaseReceipt' },
2022-11-09 18:42:00 +00:00
],
label: t`Ref Type`,
fieldname: 'referenceType',
placeholder: t`Ref Type`,
},
{
fieldtype: 'DynamicLink',
label: t`Ref Name`,
references: 'referenceType',
placeholder: t`Ref Name`,
emptyMessage: t`Change Ref Type`,
fieldname: 'referenceName',
},
{
fieldtype: 'Link',
target: 'Item',
placeholder: t`Item`,
label: t`Item`,
fieldname: 'item',
},
{
fieldtype: 'Link',
target: 'Location',
placeholder: t`Location`,
label: t`Location`,
fieldname: 'location',
},
{
fieldtype: 'Date',
placeholder: t`From Date`,
label: t`From Date`,
fieldname: 'fromDate',
},
{
fieldtype: 'Date',
placeholder: t`To Date`,
label: t`To Date`,
fieldname: 'toDate',
},
{
fieldtype: 'Select',
label: t`Group By`,
fieldname: 'groupBy',
options: [
{ label: t`None`, value: 'none' },
{ label: t`Item`, value: 'item' },
{ label: t`Location`, value: 'location' },
],
},
{
fieldtype: 'Check',
label: t`Ascending Order`,
fieldname: 'ascending',
},
] as Field[];
2022-11-03 14:55:08 +00:00
}
getActions(): Action[] {
return getCommonExportActions(this);
}
}