fix: filtering the party ledger

This commit is contained in:
Gadha2311
2025-11-05 10:40:12 +05:30
parent 832be73357
commit 1c10b1bb9d
6 changed files with 50 additions and 479 deletions
-437
View File
@@ -1,437 +0,0 @@
import { Fyo, t } from 'fyo';
import { DateTime } from 'luxon';
import { ModelNameEnum } from 'models/types';
import { LedgerReport } from 'reports/LedgerReport';
import {
ColumnField,
GroupedMap,
LedgerEntry,
ReportData,
ReportRow,
} from 'reports/types';
import { Field, FieldTypeEnum } from 'schemas/types';
import { QueryFilter } from 'utils/db/types';
type ReferenceType =
| ModelNameEnum.SalesInvoice
| ModelNameEnum.PurchaseInvoice
| ModelNameEnum.Payment
| ModelNameEnum.JournalEntry
| ModelNameEnum.Shipment
| ModelNameEnum.PurchaseReceipt
| 'All';
export class PartyLedger extends LedgerReport {
static title = t`Party Ledger`;
static reportName = 'party-ledger';
usePagination = true;
loading = false;
ascending = false;
reverted = false;
referenceType: ReferenceType = 'All';
groupBy: 'none' | 'party' | 'account' | 'referenceName' = 'none';
role = '';
_rawData: LedgerEntry[] = [];
constructor(fyo: Fyo) {
super(fyo);
}
setDefaultFilters() {
if (!this.toDate) {
this.toDate = DateTime.now().plus({ days: 1 }).toISODate();
this.fromDate = DateTime.now().minus({ years: 1 }).toISODate();
}
}
async setReportData(filter?: string, force?: boolean) {
this.loading = true;
let sort = true;
if (force || filter !== 'grouped' || this._rawData.length === 0) {
await this._setRawData();
if (this.role && this.role !== 'Both') {
this._rawData = this._rawData.filter(
(entry) => entry.role === this.role
);
}
sort = false;
}
const map = this._getGroupedMap(sort);
this._setIndexOnEntries(map);
const { totalDebit, totalCredit } = this._getTotalsAndSetBalance(map);
const consolidated = this._consolidateEntries(map);
if (consolidated.at(-1)?.name !== -3) {
this._pushBlankEntry(consolidated);
}
consolidated.push({
name: -2,
account: t`Closing`,
date: null,
debit: totalDebit,
credit: totalCredit,
balance: totalDebit - totalCredit,
referenceType: '',
referenceName: '',
party: '',
role: '',
reverted: false,
reverts: '',
});
this.reportData = this._convertEntriesToReportData(consolidated);
this.loading = false;
}
_setIndexOnEntries(map: GroupedMap) {
let i = 1;
for (const key of map.keys()) {
for (const entry of map.get(key)!) {
entry.index = String(i);
i = i + 1;
}
}
}
_convertEntriesToReportData(entries: LedgerEntry[]): ReportData {
const reportData = [];
for (const entry of entries) {
const row = this._getRowFromEntry(entry, this.columns);
reportData.push(row);
}
return reportData;
}
_getRowFromEntry(entry: LedgerEntry, columns: ColumnField[]): ReportRow {
if (entry.name === -3) {
return {
isEmpty: true,
cells: columns.map((c) => ({
rawValue: '',
value: '',
width: c.width ?? 1,
})),
};
}
const row: ReportRow = { cells: [] };
for (const col of columns) {
const align = col.align ?? 'left';
const width = col.width ?? 1;
const fieldname = col.fieldname;
let value = entry[fieldname as keyof LedgerEntry];
const rawValue = value;
if (value === null || value === undefined) {
value = '';
}
if (value instanceof Date) {
value = this.fyo.format(value, FieldTypeEnum.Date);
}
if (typeof value === 'number' && fieldname !== 'index') {
value = this.fyo.format(value, FieldTypeEnum.Currency);
}
if (typeof value === 'boolean' && fieldname === 'reverted') {
value = value ? t`Reverted` : '';
} else {
value = String(value);
}
if (fieldname === 'referenceType') {
value = this.fyo.schemaMap[value]?.label ?? value;
}
row.cells.push({
italics: entry.name === -1,
bold: entry.name === -2,
value,
rawValue,
align,
width,
});
}
return row;
}
_consolidateEntries(map: GroupedMap) {
const entries: LedgerEntry[] = [];
for (const key of map.keys()) {
entries.push(...map.get(key)!);
if (this.groupBy !== 'none') {
this._pushBlankEntry(entries);
}
}
return entries;
}
_pushBlankEntry(entries: LedgerEntry[]) {
entries.push({
name: -3,
account: '',
date: null,
debit: null,
credit: null,
balance: null,
referenceType: '',
referenceName: '',
party: '',
role: '',
reverted: false,
reverts: '',
});
}
_getTotalsAndSetBalance(map: GroupedMap) {
let totalDebit = 0;
let totalCredit = 0;
for (const key of map.keys()) {
let balance = 0;
let debit = 0;
let credit = 0;
for (const entry of map.get(key)!) {
debit += entry.debit!;
credit += entry.credit!;
const diff = entry.debit! - entry.credit!;
balance += diff;
entry.balance = balance;
}
if (this.groupBy !== 'none') {
map.get(key)?.push({
name: -1,
account: t`Total`,
date: null,
debit,
credit,
balance: debit - credit,
referenceType: '',
referenceName: '',
party: '',
role: '',
reverted: false,
reverts: '',
});
}
totalDebit += debit;
totalCredit += credit;
}
return { totalDebit, totalCredit };
}
_getQueryFilters(): QueryFilter {
const filters: QueryFilter = {};
const stringFilters = ['account', 'party', 'referenceName'];
for (const sf of stringFilters) {
const value = this[sf];
if (value === undefined || !value) {
continue;
}
filters[sf] = value as string;
}
if (this.referenceType !== 'All') {
filters.referenceType = this.referenceType;
}
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() {
const refTypeOptions = [
{ 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' },
];
if (!this.fyo.singles.AccountingSettings?.enableInventory) {
refTypeOptions.push(
{ label: t`Shipment`, value: 'Shipment' },
{ label: t`Purchase Receipt`, value: 'PurchaseReceipt' }
);
}
return [
{
fieldtype: 'Select',
options: refTypeOptions,
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: 'Account',
placeholder: t`Account`,
label: t`Account`,
fieldname: 'account',
},
{
fieldtype: 'Link',
target: 'Party',
label: t`Party`,
placeholder: t`Party`,
fieldname: 'party',
},
{
fieldtype: 'Select',
label: t`Role`,
fieldname: 'role',
default: 'Both',
options: [
{ label: t`Both`, value: 'Both' },
{ label: t`Supplier`, value: 'Supplier' },
{ label: t`Customer`, value: 'Customer' },
],
},
{
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`Party`, value: 'party' },
{ label: t`Account`, value: 'account' },
{ label: t`Reference`, value: 'referenceName' },
],
},
{
fieldtype: 'Check',
label: t`Include Cancelled`,
fieldname: 'reverted',
},
{
fieldtype: 'Check',
label: t`Ascending Order`,
fieldname: 'ascending',
},
] as Field[];
}
getColumns(): ColumnField[] {
let columns = [
{
label: '#',
fieldtype: 'Int',
fieldname: 'index',
align: 'right',
width: 0.5,
},
{
label: t`Account`,
fieldtype: 'Link',
fieldname: 'account',
width: 1.5,
},
{
label: t`Date`,
fieldtype: 'Date',
fieldname: 'date',
},
{
label: t`Debit`,
fieldtype: 'Currency',
fieldname: 'debit',
align: 'right',
width: 1.25,
},
{
label: t`Credit`,
fieldtype: 'Currency',
fieldname: 'credit',
align: 'right',
width: 1.25,
},
{
label: t`Balance`,
fieldtype: 'Currency',
fieldname: 'balance',
align: 'right',
width: 1.25,
},
{
label: t`Party`,
fieldtype: 'Link',
fieldname: 'party',
},
{
label: t`Party Type`,
fieldtype: 'Data',
fieldname: 'role',
},
{
label: t`Ref Name`,
fieldtype: 'Data',
fieldname: 'referenceName',
},
{
label: t`Ref Type`,
fieldtype: 'Data',
fieldname: 'referenceType',
},
{
label: t`Reverted`,
fieldtype: 'Check',
fieldname: 'reverted',
},
] as ColumnField[];
if (!this.reverted) {
columns = columns.filter((f) => f.fieldname !== 'reverted');
}
return columns;
}
}
-2
View File
@@ -2,7 +2,6 @@ import { BalanceSheet } from './BalanceSheet/BalanceSheet';
import { GeneralLedger } from './GeneralLedger/GeneralLedger';
import { GSTR1 } from './GoodsAndServiceTax/GSTR1';
import { GSTR2 } from './GoodsAndServiceTax/GSTR2';
import { PartyLedger } from './PartyLedger/PartyLedger';
import { ProfitAndLoss } from './ProfitAndLoss/ProfitAndLoss';
import { TrialBalance } from './TrialBalance/TrialBalance';
import { StockBalance } from './inventory/StockBalance';
@@ -10,7 +9,6 @@ import { StockLedger } from './inventory/StockLedger';
export const reports = {
GeneralLedger,
PartyLedger,
ProfitAndLoss,
BalanceSheet,
TrialBalance,
+42 -1
View File
@@ -55,6 +55,15 @@
class="w-4 h-4"
></feather-icon>
</Button>
<DropdownWithActions
v-if="canShowViewButton"
type="secondary"
:actions="viewActions"
>
<p>{{ t`View` }}</p>
</DropdownWithActions>
<DropdownWithActions
v-for="group of groupedActions"
:key="group.label"
@@ -199,6 +208,7 @@ import {
getFieldsGroupedByTabAndSection,
getFormRoute,
getGroupedActionsForDoc,
getLedgerLink,
isPrintable,
routeTo,
} from 'src/utils/ui';
@@ -207,6 +217,7 @@ import { computed, defineComponent, inject, nextTick, ref } from 'vue';
import CommonFormSection from './CommonFormSection.vue';
import LinkedEntries from './LinkedEntries.vue';
import RowEditForm from './RowEditForm.vue';
import { Action } from 'fyo/model/types';
export default defineComponent({
components: {
@@ -325,6 +336,36 @@ export default defineComponent({
return this.doc.inserted;
},
canShowViewButton(): boolean {
return this.schemaName === 'Party';
},
viewActions(): Action[] {
const actions: Action[] = [
{
label: this.t`General Ledger`,
action: async () => {
await this.$router.push({
path: '/report/GeneralLedger',
query: {
defaultFilters: JSON.stringify({
party: this.doc.name,
}),
},
});
},
},
];
if (this.hasDoc && this.doc.isSubmitted) {
actions.push({
label: this.t`Ledger`,
action: async () => {
const route = getLedgerLink(this.doc, 'GeneralLedger');
await this.routeTo(route);
},
});
}
return actions;
},
hasDoc(): boolean {
return this.docOrNull instanceof Doc;
},
@@ -462,7 +503,7 @@ export default defineComponent({
return;
}
this.doc.once('afterSync', async () => {
void this.doc.once('afterSync', async () => {
const route = getFormRoute(this.schemaName, this.doc.name!);
await this.$router.replace(route);
});
+7 -1
View File
@@ -127,8 +127,14 @@ export default defineComponent({
const filters = this.$route.query as Record<string, DocValue>;
const validFilters: Record<string, DocValue> = {};
if (filters.defaultFilters && typeof filters.defaultFilters === 'string') {
const parsed = JSON.parse(filters.defaultFilters);
Object.assign(validFilters, parsed);
}
for (const [key, value] of Object.entries(filters)) {
if (typeof value === 'string') {
if (key !== 'defaultFilters' && typeof value === 'string') {
validFilters[key] = value;
}
}
-15
View File
@@ -123,21 +123,6 @@ function getReportSidebar() {
name: 'general-ledger',
route: '/report/GeneralLedger',
},
{
label: t`Party Ledger`,
name: 'party-ledger',
route: '/report/PartyLedger',
},
{
label: t`Customer Ledger`,
name: 'customer-ledger',
route: '/report/PartyLedger?role=Customer',
},
{
label: t`Supplier Ledger`,
name: 'supplier-ledger',
route: '/report/PartyLedger?role=Supplier',
},
{
label: t`Profit And Loss`,
name: 'profit-and-loss',
+1 -23
View File
@@ -193,7 +193,6 @@ export function getActionsForDoc(doc?: Doc): Action[] {
...getActions(doc),
getDuplicateAction(doc),
getNewAction(doc),
getViewPartyLedgerAction(doc),
getDeleteAction(doc),
getCancelAction(doc),
];
@@ -307,28 +306,6 @@ function getNewAction(doc: Doc): Action {
};
}
function getViewPartyLedgerAction(doc: Doc): Action {
return {
label: t`View Party Ledger`,
group: t`Create`,
condition: (doc: Doc) => doc.schemaName === 'Party',
async action() {
try {
const role = doc.role as string;
const query: Record<string, string> = {
party: encodeURIComponent(doc.name!),
};
if (role && role !== 'Both') {
query.role = role;
}
await routeTo({ path: '/report/PartyLedger', query });
} catch (err) {
await handleErrorWithDialog(err as Error, doc);
}
},
};
}
export function getFieldsGroupedByTabAndSection(
schema: Schema,
doc: Doc
@@ -1072,3 +1049,4 @@ export async function getSavePath(name: string, extention: string) {
return { canceled, filePath };
}
export { getLedgerLink };