mirror of
https://github.com/frappe/books.git
synced 2026-08-24 10:04:45 -05:00
fix: filtering the party ledger
This commit is contained in:
@@ -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,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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user