mirror of
https://github.com/frappe/books.git
synced 2024-12-22 02:49:03 +00:00
incr: add GeneralLedger business logic
This commit is contained in:
parent
86c4889959
commit
1e88c5511f
2
META.md
2
META.md
@ -51,7 +51,7 @@ individual ones, check the `README.md` in those subdirectories:
|
||||
| `build` | _server_ | Build specific files not used unless building the project |
|
||||
| `translations` | _server_ | Collection of csv files containing translations |
|
||||
| `src` | _client_ | Code that mainly deals with the view layer (all `.vue` are stored here) |
|
||||
| `reports` | _client_ | Collection of logic code and view layer config files for displaying reports. |
|
||||
| `reports` | _client\*_ | Collection of logic code and view layer config files for displaying reports. |
|
||||
| `models` | _client\*_ | Collection of `Model.ts` files that manage the data and some business logic on the client side. |
|
||||
| `fyo` | _client\*_ | Code for the underlying library that manages the client side |
|
||||
| `utils` | _agnostic_ | Collection of code used by either sides. |
|
||||
|
@ -122,7 +122,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
async getAllRaw(
|
||||
schemaName: string,
|
||||
options: GetAllOptions = {}
|
||||
): Promise<DocValueMap[]> {
|
||||
): Promise<RawValueMap[]> {
|
||||
const all = await this.#getAll(schemaName, options);
|
||||
this.observer.trigger(`getAllRaw:${schemaName}`, options);
|
||||
return all;
|
||||
|
@ -1,97 +0,0 @@
|
||||
import { fyo } from 'src/initFyo';
|
||||
|
||||
class GeneralLedger {
|
||||
async run(params) {
|
||||
const filters = {};
|
||||
if (params.account) filters.account = params.account;
|
||||
if (params.party) filters.party = params.party;
|
||||
if (params.referenceType !== 'All')
|
||||
filters.referenceType = params.referenceType;
|
||||
if (params.referenceName) filters.referenceName = params.referenceName;
|
||||
if (params.toDate || params.fromDate) {
|
||||
filters.date = [];
|
||||
if (params.toDate) filters.date.push('<=', params.toDate);
|
||||
if (params.fromDate) filters.date.push('>=', params.fromDate);
|
||||
}
|
||||
|
||||
let data = (
|
||||
await fyo.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
fields: [
|
||||
'date',
|
||||
'account',
|
||||
'party',
|
||||
'referenceType',
|
||||
'referenceName',
|
||||
'debit',
|
||||
'credit',
|
||||
'reverted',
|
||||
],
|
||||
filters: filters,
|
||||
})
|
||||
)
|
||||
.filter((d) => !d.reverted || (d.reverted && params.reverted))
|
||||
.map((row) => {
|
||||
row.debit = row.debit.float;
|
||||
row.credit = row.credit.float;
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
return this.appendOpeningEntry(data);
|
||||
}
|
||||
|
||||
appendOpeningEntry(data) {
|
||||
let glEntries = [];
|
||||
let balance = 0,
|
||||
debitTotal = 0,
|
||||
creditTotal = 0;
|
||||
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: { template: '<b>Opening</b>' },
|
||||
party: '',
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
balance: 0,
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
});
|
||||
for (let entry of data) {
|
||||
balance += entry.debit > 0 ? entry.debit : -entry.credit;
|
||||
debitTotal += entry.debit;
|
||||
creditTotal += entry.credit;
|
||||
entry.balance = balance;
|
||||
if (entry.debit === 0) {
|
||||
entry.debit = '';
|
||||
}
|
||||
if (entry.credit === 0) {
|
||||
entry.credit = '';
|
||||
}
|
||||
glEntries.push(entry);
|
||||
}
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: { template: '<b>Total</b>' },
|
||||
party: '',
|
||||
debit: debitTotal,
|
||||
credit: creditTotal,
|
||||
balance: balance,
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
});
|
||||
glEntries.push({
|
||||
date: '',
|
||||
account: { template: '<b>Closing</b>' },
|
||||
party: '',
|
||||
debit: debitTotal,
|
||||
credit: creditTotal,
|
||||
balance: balance,
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
});
|
||||
return glEntries;
|
||||
}
|
||||
}
|
||||
|
||||
export default GeneralLedger;
|
484
reports/GeneralLedger/GeneralLedger.ts
Normal file
484
reports/GeneralLedger/GeneralLedger.ts
Normal file
@ -0,0 +1,484 @@
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { Action } from 'fyo/model/types';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { Report } from 'reports/Report';
|
||||
import { ColumnField, ReportData } from 'reports/types';
|
||||
import { Field, FieldTypeEnum, RawValue } from 'schemas/types';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
|
||||
interface RawLedgerEntry {
|
||||
name: string;
|
||||
account: string;
|
||||
date: string;
|
||||
debit: string;
|
||||
credit: string;
|
||||
referenceType: string;
|
||||
referenceName: string;
|
||||
party: string;
|
||||
reverted: number;
|
||||
reverts: string;
|
||||
[key: string]: RawValue;
|
||||
}
|
||||
|
||||
interface LedgerEntry {
|
||||
name: number;
|
||||
account: string;
|
||||
date: Date | null;
|
||||
debit: Money | null;
|
||||
credit: Money | null;
|
||||
balance: Money | null;
|
||||
referenceType: string;
|
||||
referenceName: string;
|
||||
party: string;
|
||||
reverted: boolean;
|
||||
reverts: string;
|
||||
}
|
||||
|
||||
type GroupedMap = Map<string, LedgerEntry[]>;
|
||||
|
||||
export class GeneralLedger extends Report {
|
||||
static title = t`General Ledger`;
|
||||
static reportName = 'general-ledger';
|
||||
|
||||
ascending!: boolean;
|
||||
groupBy!: 'none' | 'party' | 'account' | 'referenceName';
|
||||
_rawData: LedgerEntry[] = [];
|
||||
|
||||
constructor(fyo: Fyo) {
|
||||
super(fyo);
|
||||
|
||||
if (!this.toField) {
|
||||
this.toField = DateTime.now().toISODate();
|
||||
this.fromField = DateTime.now().minus({ years: 1 }).toISODate();
|
||||
}
|
||||
}
|
||||
|
||||
async setReportData(filter?: string) {
|
||||
if (filter !== 'grouped' || this._rawData.length === 0) {
|
||||
console.time('_setRawData');
|
||||
await this._setRawData();
|
||||
console.timeEnd('_setRawData');
|
||||
}
|
||||
|
||||
console.time('_getGroupedMap');
|
||||
const map = this._getGroupedMap();
|
||||
console.timeEnd('_getGroupedMap');
|
||||
|
||||
console.time('_getTotalsAndSetBalance');
|
||||
const { totalDebit, totalCredit } = this._getTotalsAndSetBalance(map);
|
||||
console.timeEnd('_getTotalsAndSetBalance');
|
||||
|
||||
console.time('_consolidateEntries');
|
||||
const consolidated = this._consolidateEntries(map);
|
||||
console.timeEnd('_consolidateEntries');
|
||||
|
||||
/**
|
||||
* Push a blank row if last row isn't blank
|
||||
*/
|
||||
if (consolidated.at(-1)!.name !== -3) {
|
||||
this._pushBlankEntry(consolidated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the closing row
|
||||
*/
|
||||
consolidated.push({
|
||||
name: -2, // Bold
|
||||
account: t`Closing`,
|
||||
date: null,
|
||||
debit: totalDebit,
|
||||
credit: totalCredit,
|
||||
balance: totalDebit.sub(totalCredit),
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
party: '',
|
||||
reverted: false,
|
||||
reverts: '',
|
||||
});
|
||||
|
||||
console.time('_convertEntriesToReportData');
|
||||
this.reportData = this._convertEntriesToReportData(consolidated);
|
||||
console.timeEnd('_convertEntriesToReportData');
|
||||
}
|
||||
|
||||
_convertEntriesToReportData(entries: LedgerEntry[]): ReportData {
|
||||
const reportData = [];
|
||||
const fieldnames = this.columns.map((f) => f.fieldname);
|
||||
for (const entry of entries) {
|
||||
const row = this._getRowFromEntry(entry, fieldnames);
|
||||
reportData.push(row);
|
||||
}
|
||||
|
||||
return reportData;
|
||||
}
|
||||
|
||||
_getRowFromEntry(entry: LedgerEntry, fieldnames: string[]) {
|
||||
if (entry.name === -3) {
|
||||
return Array(fieldnames.length).fill({ value: '' });
|
||||
}
|
||||
|
||||
const row = [];
|
||||
for (const n of fieldnames) {
|
||||
let value = entry[n as keyof LedgerEntry];
|
||||
if (value === null || value === undefined) {
|
||||
row.push({ value: '' });
|
||||
continue;
|
||||
}
|
||||
|
||||
let align = 'left';
|
||||
if (value instanceof Date) {
|
||||
value = this.fyo.format(value, FieldTypeEnum.Date);
|
||||
}
|
||||
|
||||
if (isPesa(value)) {
|
||||
align = 'right';
|
||||
value = this.fyo.format(value, FieldTypeEnum.Currency);
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean' && n === 'reverted' && value) {
|
||||
value = t`Reverted`;
|
||||
}
|
||||
|
||||
row.push({
|
||||
italics: entry.name === -1,
|
||||
bold: entry.name === -2,
|
||||
value,
|
||||
align,
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
_consolidateEntries(map: GroupedMap) {
|
||||
const entries: LedgerEntry[] = [];
|
||||
for (const key of map.keys()) {
|
||||
entries.push(...map.get(key)!);
|
||||
|
||||
/**
|
||||
* Add blank row for spacing if groupBy
|
||||
*/
|
||||
if (this.groupBy !== 'none') {
|
||||
this._pushBlankEntry(entries);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
_pushBlankEntry(entries: LedgerEntry[]) {
|
||||
entries.push({
|
||||
name: -3, // Empty
|
||||
account: '',
|
||||
date: null,
|
||||
debit: null,
|
||||
credit: null,
|
||||
balance: null,
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
party: '',
|
||||
reverted: false,
|
||||
reverts: '',
|
||||
});
|
||||
}
|
||||
|
||||
_getTotalsAndSetBalance(map: GroupedMap) {
|
||||
let totalDebit = this.fyo.pesa(0);
|
||||
let totalCredit = this.fyo.pesa(0);
|
||||
|
||||
for (const key of map.keys()) {
|
||||
let balance = this.fyo.pesa(0);
|
||||
let debit = this.fyo.pesa(0);
|
||||
let credit = this.fyo.pesa(0);
|
||||
|
||||
for (const entry of map.get(key)!) {
|
||||
debit = debit.add(entry.debit!);
|
||||
credit = credit.add(entry.credit!);
|
||||
|
||||
const diff = entry.debit!.sub(entry.credit!);
|
||||
balance = balance.add(diff);
|
||||
entry.balance = balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total row incase groupBy is used
|
||||
*/
|
||||
if (this.groupBy !== 'none') {
|
||||
map.get(key)?.push({
|
||||
name: -1, // Italics
|
||||
account: t`Total`,
|
||||
date: null,
|
||||
debit,
|
||||
credit,
|
||||
balance: debit.sub(credit),
|
||||
referenceType: '',
|
||||
referenceName: '',
|
||||
party: '',
|
||||
reverted: false,
|
||||
reverts: '',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Total debit and credit for the final row
|
||||
*/
|
||||
totalDebit = totalDebit.add(debit);
|
||||
totalCredit = totalCredit.add(credit);
|
||||
}
|
||||
|
||||
return { totalDebit, totalCredit };
|
||||
}
|
||||
|
||||
_getGroupedMap(): GroupedMap {
|
||||
let groupBy: keyof LedgerEntry = 'referenceName';
|
||||
if (this.groupBy !== 'none') {
|
||||
groupBy = this.groupBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort rows by ascending or descending
|
||||
*/
|
||||
this._rawData.sort((a, b) => {
|
||||
if (this.ascending) {
|
||||
return a.name - b.name;
|
||||
}
|
||||
|
||||
return b.name - a.name;
|
||||
});
|
||||
|
||||
/**
|
||||
* Map remembers the order of insertion
|
||||
* ∴ presorting maintains grouping order
|
||||
*/
|
||||
const map: GroupedMap = new Map();
|
||||
for (const entry of this._rawData) {
|
||||
const groupingKey = entry[groupBy];
|
||||
if (!map.has(groupingKey)) {
|
||||
map.set(groupingKey, []);
|
||||
}
|
||||
|
||||
map.get(groupingKey)!.push(entry);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
async _setRawData() {
|
||||
const fields = [
|
||||
'name',
|
||||
'account',
|
||||
'date',
|
||||
'debit',
|
||||
'credit',
|
||||
'referenceType',
|
||||
'referenceName',
|
||||
'party',
|
||||
'reverted',
|
||||
'reverts',
|
||||
];
|
||||
|
||||
const filters = this._getFilters();
|
||||
const entries = (await this.fyo.db.getAllRaw(
|
||||
ModelNameEnum.AccountingLedgerEntry,
|
||||
{
|
||||
fields,
|
||||
filters,
|
||||
}
|
||||
)) as RawLedgerEntry[];
|
||||
|
||||
this._rawData = entries.map((entry) => {
|
||||
return {
|
||||
name: parseInt(entry.name),
|
||||
account: entry.account,
|
||||
date: new Date(entry.date),
|
||||
debit: this.fyo.pesa(entry.debit),
|
||||
credit: this.fyo.pesa(entry.credit),
|
||||
balance: this.fyo.pesa(0),
|
||||
referenceType: entry.referenceType,
|
||||
referenceName: entry.referenceName,
|
||||
party: entry.party,
|
||||
reverted: Boolean(entry.reverted),
|
||||
reverts: entry.reverts,
|
||||
} as LedgerEntry;
|
||||
});
|
||||
}
|
||||
|
||||
_getFilters(): QueryFilter {
|
||||
const filters: QueryFilter = {};
|
||||
const stringFilters = ['account', 'party', 'referenceName'];
|
||||
|
||||
for (const sf in stringFilters) {
|
||||
const value = this[sf];
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filters[sf] = value as string;
|
||||
}
|
||||
|
||||
if (this.referenceType !== 'All') {
|
||||
filters.referenceType = this.referenceType as string;
|
||||
}
|
||||
|
||||
if (this.toDate) {
|
||||
filters.date ??= [];
|
||||
(filters.date as string[]).push('<=', this.toDate as string);
|
||||
}
|
||||
|
||||
if (this.fromDate) {
|
||||
filters.date ??= [];
|
||||
(filters.date as string[]).push('>=', this.fromDate as string);
|
||||
}
|
||||
|
||||
if (!this.reverted) {
|
||||
filters.reverted = false;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
return [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
{ label: t`All`, value: 'All' },
|
||||
{ label: t`Sales Invoices`, value: 'SalesInvoice' },
|
||||
{ label: t`Purchase Invoices`, value: 'PurchaseInvoice' },
|
||||
{ label: t`Payments`, value: 'Payment' },
|
||||
{ label: t`Journal Entries`, value: 'JournalEntry' },
|
||||
],
|
||||
|
||||
label: t`Reference Type`,
|
||||
fieldname: 'referenceType',
|
||||
placeholder: t`Reference Type`,
|
||||
default: 'All',
|
||||
},
|
||||
{
|
||||
fieldtype: 'DynamicLink',
|
||||
placeholder: t`Reference Name`,
|
||||
references: 'referenceType',
|
||||
label: t`Reference Name`,
|
||||
fieldname: 'referenceName',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
placeholder: t`Account`,
|
||||
label: t`Account`,
|
||||
fieldname: 'account',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
label: t`Party`,
|
||||
placeholder: t`Party`,
|
||||
fieldname: 'party',
|
||||
},
|
||||
{
|
||||
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: 'Check',
|
||||
default: false,
|
||||
label: t`Cancelled`,
|
||||
fieldname: 'reverted',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Check',
|
||||
default: false,
|
||||
label: t`Ascending`,
|
||||
fieldname: 'ascending',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Check',
|
||||
default: 'none',
|
||||
label: t`Group By`,
|
||||
fieldname: 'groupBy',
|
||||
options: [
|
||||
{ label: t`None`, value: 'none' },
|
||||
{ label: t`Party`, value: 'party' },
|
||||
{ label: t`Account`, value: 'account' },
|
||||
{ label: t`Reference`, value: 'referenceName' },
|
||||
],
|
||||
},
|
||||
] as Field[];
|
||||
}
|
||||
|
||||
getColumns(): ColumnField[] {
|
||||
let columns = [
|
||||
{
|
||||
label: t`Account`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'account',
|
||||
width: 1.5,
|
||||
},
|
||||
{
|
||||
label: t`Date`,
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'date',
|
||||
width: 0.75,
|
||||
},
|
||||
{
|
||||
label: t`Debit`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'debit',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Credit`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'credit',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Balance`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'balance',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Reference Type`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'referenceType',
|
||||
},
|
||||
{
|
||||
label: t`Reference Name`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'referenceName',
|
||||
},
|
||||
{
|
||||
label: t`Party`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'party',
|
||||
},
|
||||
{
|
||||
label: t`Reverted`,
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'reverted',
|
||||
},
|
||||
] as ColumnField[];
|
||||
|
||||
if (!this.reverted) {
|
||||
columns = columns.filter((f) => f.fieldname !== 'reverted');
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
getActions(): Action[] {
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
import { t } from 'fyo';
|
||||
import Avatar from 'src/components/Avatar.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import getCommonExportActions from '../commonExporter';
|
||||
|
||||
export function getPartyWithAvatar(partyName) {
|
||||
return {
|
||||
data() {
|
||||
return {
|
||||
imageURL: null,
|
||||
label: null,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
Avatar,
|
||||
},
|
||||
async mounted() {
|
||||
const p = await fyo.db.get('Party', partyName);
|
||||
this.imageURL = p.image;
|
||||
this.label = partyName;
|
||||
},
|
||||
template: `
|
||||
<div class="flex items-center" v-if="label">
|
||||
<Avatar class="flex-shrink-0" :imageURL="imageURL" :label="label" size="sm" />
|
||||
<span class="ml-2 truncate">{{ label }}</span>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
let title = t`General Ledger`;
|
||||
|
||||
const viewConfig = {
|
||||
title,
|
||||
filterFields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
{ label: t`All References`, value: 'All' },
|
||||
{ label: t`Invoices`, value: 'SalesInvoice' },
|
||||
{ label: t`Bills`, value: 'PurchaseInvoice' },
|
||||
{ label: t`Payment`, value: 'Payment' },
|
||||
{ label: t`Journal Entry`, value: 'JournalEntry' },
|
||||
],
|
||||
size: 'small',
|
||||
label: t`Reference Type`,
|
||||
fieldname: 'referenceType',
|
||||
placeholder: t`Reference Type`,
|
||||
default: 'All',
|
||||
},
|
||||
{
|
||||
fieldtype: 'DynamicLink',
|
||||
size: 'small',
|
||||
placeholder: t`Reference Name`,
|
||||
references: 'referenceType',
|
||||
label: t`Reference Name`,
|
||||
fieldname: 'referenceName',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Account',
|
||||
size: 'small',
|
||||
placeholder: t`Account`,
|
||||
label: t`Account`,
|
||||
fieldname: 'account',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Link',
|
||||
target: 'Party',
|
||||
label: t`Party`,
|
||||
size: 'small',
|
||||
placeholder: t`Party`,
|
||||
fieldname: 'party',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: t`From Date`,
|
||||
label: t`From Date`,
|
||||
fieldname: 'fromDate',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Date',
|
||||
size: 'small',
|
||||
placeholder: t`To Date`,
|
||||
label: t`To Date`,
|
||||
fieldname: 'toDate',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Check',
|
||||
size: 'small',
|
||||
default: 0,
|
||||
label: t`Cancelled`,
|
||||
fieldname: 'reverted',
|
||||
},
|
||||
],
|
||||
method: 'general-ledger',
|
||||
actions: getCommonExportActions('general-ledger'),
|
||||
getColumns() {
|
||||
return [
|
||||
{
|
||||
label: t`Account`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'account',
|
||||
width: 1.5,
|
||||
},
|
||||
{
|
||||
label: t`Date`,
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'date',
|
||||
width: 0.75,
|
||||
},
|
||||
{
|
||||
label: t`Debit`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'debit',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Credit`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'credit',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Balance`,
|
||||
fieldtype: 'Currency',
|
||||
fieldname: 'balance',
|
||||
width: 1.25,
|
||||
},
|
||||
{
|
||||
label: t`Reference Type`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'referenceType',
|
||||
},
|
||||
{
|
||||
label: t`Reference Name`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'referenceName',
|
||||
},
|
||||
{
|
||||
label: t`Party`,
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'party',
|
||||
component(cellValue) {
|
||||
return getPartyWithAvatar(cellValue);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default viewConfig;
|
6
reports/README.md
Normal file
6
reports/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Reports
|
||||
|
||||
Reports are a view of stored data, the code here doesn't alter any data.
|
||||
|
||||
All reports should extend the `Report` class in `reports/Report.ts`, depending
|
||||
on the report it may have custom `.vue` files.
|
72
reports/Report.ts
Normal file
72
reports/Report.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { Action } from 'fyo/model/types';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import { Field, RawValue } from 'schemas/types';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { ColumnField, ReportData } from './types';
|
||||
|
||||
export abstract class Report extends Observable<RawValue> {
|
||||
static title: string;
|
||||
static reportName: string;
|
||||
|
||||
fyo: Fyo;
|
||||
columns: ColumnField[];
|
||||
filters: Field[];
|
||||
reportData: ReportData;
|
||||
|
||||
abstract getActions(): Action[];
|
||||
abstract getFilters(): Field[];
|
||||
|
||||
constructor(fyo: Fyo) {
|
||||
super();
|
||||
this.fyo = fyo;
|
||||
this.reportData = [];
|
||||
this.filters = this.getFilters();
|
||||
this.columns = this.getColumns();
|
||||
this.initializeFilters();
|
||||
}
|
||||
|
||||
get filterMap() {
|
||||
const filterMap: Record<string, RawValue> = {};
|
||||
for (const { fieldname } of this.filters) {
|
||||
const value = this.get(fieldname);
|
||||
if (getIsNullOrUndef(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filterMap[fieldname] = value;
|
||||
}
|
||||
|
||||
return filterMap;
|
||||
}
|
||||
|
||||
async set(key: string, value: RawValue) {
|
||||
const field = this.filters.find((f) => f.fieldname === key);
|
||||
if (field === undefined || value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevValue = this[key];
|
||||
if (prevValue === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[key] = value;
|
||||
this.columns = this.getColumns();
|
||||
await this.setReportData(key);
|
||||
}
|
||||
|
||||
initializeFilters() {
|
||||
for (const field of this.filters) {
|
||||
if (!field.default) {
|
||||
this[field.fieldname] = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
this[field.fieldname] = field.default;
|
||||
}
|
||||
}
|
||||
|
||||
abstract getColumns(): ColumnField[];
|
||||
abstract setReportData(filter?: string): Promise<void>;
|
||||
}
|
2
reports/index.ts
Normal file
2
reports/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { GeneralLedger } from './GeneralLedger/GeneralLedger';
|
||||
export { GeneralLedger };
|
@ -1,14 +1,19 @@
|
||||
import { AccountRootType } from 'models/baseModels/Account/types';
|
||||
import { BaseField } from 'schemas/types';
|
||||
|
||||
export type ExportExtension = 'csv' | 'json';
|
||||
|
||||
export interface ReportData {
|
||||
rows: unknown[];
|
||||
columns: unknown[];
|
||||
export interface ReportCell {
|
||||
bold?: boolean;
|
||||
italics?: boolean;
|
||||
align?: 'left' | 'right' | 'center';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export abstract class Report {
|
||||
abstract run(filters: Record<string, unknown>): ReportData;
|
||||
export type ReportRow = ReportCell[];
|
||||
export type ReportData = ReportRow[];
|
||||
export interface ColumnField extends BaseField {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export type BalanceType = 'Credit' | 'Debit';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ConfigKeys } from 'fyo/core/types';
|
||||
import { DateTime } from 'luxon';
|
||||
import { GeneralLedger } from 'reports';
|
||||
import { IPC_ACTIONS } from 'utils/messages';
|
||||
import { App as VueApp, createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
@ -108,4 +108,4 @@ function setOnWindow() {
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.DateTime = DateTime;
|
||||
window.GL = GeneralLedger;
|
||||
|
Loading…
Reference in New Issue
Block a user