mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +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 |
|
| `build` | _server_ | Build specific files not used unless building the project |
|
||||||
| `translations` | _server_ | Collection of csv files containing translations |
|
| `translations` | _server_ | Collection of csv files containing translations |
|
||||||
| `src` | _client_ | Code that mainly deals with the view layer (all `.vue` are stored here) |
|
| `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. |
|
| `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 |
|
| `fyo` | _client\*_ | Code for the underlying library that manages the client side |
|
||||||
| `utils` | _agnostic_ | Collection of code used by either sides. |
|
| `utils` | _agnostic_ | Collection of code used by either sides. |
|
||||||
|
@ -122,7 +122,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
async getAllRaw(
|
async getAllRaw(
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
options: GetAllOptions = {}
|
options: GetAllOptions = {}
|
||||||
): Promise<DocValueMap[]> {
|
): Promise<RawValueMap[]> {
|
||||||
const all = await this.#getAll(schemaName, options);
|
const all = await this.#getAll(schemaName, options);
|
||||||
this.observer.trigger(`getAllRaw:${schemaName}`, options);
|
this.observer.trigger(`getAllRaw:${schemaName}`, options);
|
||||||
return all;
|
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 { AccountRootType } from 'models/baseModels/Account/types';
|
||||||
|
import { BaseField } from 'schemas/types';
|
||||||
|
|
||||||
export type ExportExtension = 'csv' | 'json';
|
export type ExportExtension = 'csv' | 'json';
|
||||||
|
|
||||||
export interface ReportData {
|
export interface ReportCell {
|
||||||
rows: unknown[];
|
bold?: boolean;
|
||||||
columns: unknown[];
|
italics?: boolean;
|
||||||
|
align?: 'left' | 'right' | 'center';
|
||||||
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Report {
|
export type ReportRow = ReportCell[];
|
||||||
abstract run(filters: Record<string, unknown>): ReportData;
|
export type ReportData = ReportRow[];
|
||||||
|
export interface ColumnField extends BaseField {
|
||||||
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BalanceType = 'Credit' | 'Debit';
|
export type BalanceType = 'Credit' | 'Debit';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { ConfigKeys } from 'fyo/core/types';
|
import { ConfigKeys } from 'fyo/core/types';
|
||||||
import { DateTime } from 'luxon';
|
import { GeneralLedger } from 'reports';
|
||||||
import { IPC_ACTIONS } from 'utils/messages';
|
import { IPC_ACTIONS } from 'utils/messages';
|
||||||
import { App as VueApp, createApp } from 'vue';
|
import { App as VueApp, createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
@ -108,4 +108,4 @@ function setOnWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.DateTime = DateTime;
|
window.GL = GeneralLedger;
|
||||||
|
Loading…
Reference in New Issue
Block a user