Merge pull request #229 from bigcapitalhq/abouhuolia/big-45-receivablepayable-again-report-issue

fix AP/AR aging summary issue
This commit is contained in:
Ahmed Bouhuolia
2023-08-27 00:58:17 +02:00
committed by GitHub
24 changed files with 599 additions and 235 deletions

View File

@@ -33,10 +33,13 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
return [
...this.sheetNumberFormatValidationSchema,
query('as_date').optional().isISO8601(),
query('aging_days_before').optional().isNumeric().toInt(),
query('aging_periods').optional().isNumeric().toInt(),
query('aging_days_before').default(30).isInt({ max: 500 }).toInt(),
query('aging_periods').default(3).isInt({ max: 12 }).toInt(),
query('vendors_ids').optional().isArray({ min: 1 }),
query('vendors_ids.*').isInt({ min: 1 }).toInt(),
query('none_zero').default(true).isBoolean().toBoolean(),
// Filtering by branches.
@@ -53,15 +56,36 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC
const filter = this.matchedQueryData(req);
try {
const { data, columns, query, meta } =
await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
switch (acceptType) {
case 'application/json+table':
const table = await this.APAgingSummaryService.APAgingSummaryTable(
tenantId,
filter
);
return res.status(200).send({
table: {
rows: table.rows,
columns: table.columns,
},
meta: table.meta,
query: table.query,
});
break;
default:
const { data, columns, query, meta } =
await this.APAgingSummaryService.APAgingSummary(tenantId, filter);
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
break;
}
} catch (error) {
next(error);
}

View File

@@ -36,8 +36,8 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
query('as_date').optional().isISO8601(),
query('aging_days_before').optional().isInt({ max: 500 }).toInt(),
query('aging_periods').optional().isInt({ max: 12 }).toInt(),
query('aging_days_before').default(30).isInt({ max: 500 }).toInt(),
query('aging_periods').default(3).isInt({ max: 12 }).toInt(),
query('customers_ids').optional().isArray({ min: 1 }),
query('customers_ids.*').isInt({ min: 1 }).toInt(),
@@ -58,15 +58,36 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC
const filter = this.matchedQueryData(req);
try {
const { data, columns, query, meta } =
await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
switch (acceptType) {
case 'application/json+table':
const table = await this.ARAgingSummaryService.ARAgingSummaryTable(
tenantId,
filter
);
return res.status(200).send({
table: {
rows: table.rows,
columns: table.columns,
},
meta: table.meta,
query: table.query,
});
break;
default:
const { data, columns, query, meta } =
await this.ARAgingSummaryService.ARAgingSummary(tenantId, filter);
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
break;
}
} catch (error) {
console.log(error);
}

View File

@@ -1,51 +1,36 @@
import {
IAgingPeriod,
IAgingPeriodTotal,
IAgingAmount
IAgingAmount,
IAgingSummaryQuery,
IAgingSummaryTotal,
IAgingSummaryContact,
IAgingSummaryData,
} from './AgingReport';
import {
INumberFormatQuery
} from './FinancialStatements';
import { INumberFormatQuery } from './FinancialStatements';
export interface IAPAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
export interface IAPAgingSummaryQuery extends IAgingSummaryQuery {
vendorsIds: number[];
noneZero: boolean;
branchesIds?: number[]
}
export interface IAPAgingSummaryVendor {
vendorName: string,
current: IAgingAmount,
aging: IAgingPeriodTotal[],
total: IAgingAmount,
};
export interface IAPAgingSummaryVendor extends IAgingSummaryContact {
vendorName: string;
}
export interface IAPAgingSummaryTotal {
current: IAgingAmount,
aging: IAgingPeriodTotal[],
total: IAgingAmount,
};
export interface IAPAgingSummaryTotal extends IAgingSummaryTotal {}
export interface IAPAgingSummaryData {
vendors: IAPAgingSummaryVendor[],
total: IAPAgingSummaryTotal,
};
export interface IAPAgingSummaryData extends IAgingSummaryData {
vendors: IAPAgingSummaryVendor[];
}
export type IAPAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta {
baseCurrency: string,
organizationName: string,
baseCurrency: string;
organizationName: string;
}
export interface IAPAgingSummaryMeta {
baseCurrency: string,
organizationName: string,
}
baseCurrency: string;
organizationName: string;
}

View File

@@ -1,37 +1,28 @@
import { IAgingPeriod, IAgingPeriodTotal, IAgingAmount } from './AgingReport';
import { INumberFormatQuery } from './FinancialStatements';
import {
IAgingPeriod,
IAgingSummaryQuery,
IAgingSummaryTotal,
IAgingSummaryContact,
IAgingSummaryData,
} from './AgingReport';
export interface IARAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
export interface IARAgingSummaryQuery extends IAgingSummaryQuery {
customersIds: number[];
branchesIds: number[];
noneZero: boolean;
}
export interface IARAgingSummaryCustomer {
export interface IARAgingSummaryCustomer extends IAgingSummaryContact {
customerName: string;
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IARAgingSummaryTotal {
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IARAgingSummaryTotal extends IAgingSummaryTotal {}
export interface IARAgingSummaryData {
export interface IARAgingSummaryData extends IAgingSummaryData {
customers: IARAgingSummaryCustomer[];
total: IARAgingSummaryTotal;
}
export type IARAgingSummaryColumns = IAgingPeriod[];
export interface IARAgingSummaryMeta {
organizationName: string,
baseCurrency: string,
}
organizationName: string;
baseCurrency: string;
}

View File

@@ -1,6 +1,9 @@
import { INumberFormatQuery } from './FinancialStatements';
export interface IAgingPeriodTotal extends IAgingPeriod {
total: IAgingAmount;
};
}
export interface IAgingAmount {
amount: number;
@@ -20,3 +23,22 @@ export interface IAgingSummaryContact {
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IAgingSummaryQuery {
asDate: Date | string;
agingDaysBefore: number;
agingPeriods: number;
numberFormat: INumberFormatQuery;
branchesIds: number[];
noneZero: boolean;
}
export interface IAgingSummaryTotal {
current: IAgingAmount;
aging: IAgingPeriodTotal[];
total: IAgingAmount;
}
export interface IAgingSummaryData {
total: IAgingSummaryTotal;
}

View File

@@ -5,6 +5,7 @@ import TenancyService from '@/services/Tenancy/TenancyService';
import APAgingSummarySheet from './APAgingSummarySheet';
import { Tenant } from '@/system/models';
import { isEmpty } from 'lodash';
import APAgingSummaryTable from './APAgingSummaryTable';
@Service()
export default class PayableAgingSummaryService {
@@ -84,7 +85,7 @@ export default class PayableAgingSummaryService {
// Common query.
const commonQuery = (query) => {
if (isEmpty(filter.branchesIds)) {
if (!isEmpty(filter.branchesIds)) {
query.modify('filterByBranches', filter.branchesIds);
}
};
@@ -118,4 +119,21 @@ export default class PayableAgingSummaryService {
meta: this.reportMetadata(tenantId),
};
}
/**
* Retrieves A/P aging summary in table format.
* @param {number} tenantId
* @param {IAPAgingSummaryQuery} query
*/
async APAgingSummaryTable(tenantId: number, query: IAPAgingSummaryQuery) {
const report = await this.APAgingSummary(tenantId, query);
const table = new APAgingSummaryTable(report.data, query, {});
return {
columns: table.tableColumns(),
rows: table.tableRows(),
meta: report.meta,
query: report.query,
};
}
}

View File

@@ -91,7 +91,7 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
return {
vendorName: vendor.displayName,
current: this.formatTotalAmount(currentTotal),
current: this.formatAmount(currentTotal),
aging: agingPeriods,
total: this.formatTotalAmount(amount),
};

View File

@@ -0,0 +1,46 @@
import {
IAPAgingSummaryData,
IAgingSummaryQuery,
ITableColumn,
ITableColumnAccessor,
ITableRow,
} from '@/interfaces';
import AgingSummaryTable from './AgingSummaryTable';
export default class APAgingSummaryTable extends AgingSummaryTable {
readonly report: IAPAgingSummaryData;
/**
* Constructor method.
* @param {IARAgingSummaryData} data
* @param {IAgingSummaryQuery} query
* @param {any} i18n
*/
constructor(data: IAPAgingSummaryData, query: IAgingSummaryQuery, i18n: any) {
super(data, query, i18n);
}
/**
* Retrieves the contacts table rows.
* @returns {ITableRow[]}
*/
get contactsRows(): ITableRow[] {
return this.contactsNodes(this.report.vendors);
}
/**
* Contact name node accessor.
* @returns {ITableColumnAccessor}
*/
get contactNameNodeAccessor(): ITableColumnAccessor {
return { key: 'vendor_name', accessor: 'vendorName' };
}
/**
* Retrieves the contact name table column.
* @returns {ITableColumn}
*/
contactNameTableColumn = (): ITableColumn => {
return { label: 'Vendor name', key: 'vendor_name' };
};
}

View File

@@ -5,6 +5,7 @@ import { IARAgingSummaryQuery, IARAgingSummaryMeta } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import ARAgingSummarySheet from './ARAgingSummarySheet';
import { Tenant } from '@/system/models';
import ARAgingSummaryTable from './ARAgingSummaryTable';
@Service()
export default class ARAgingSummaryService {
@@ -89,12 +90,12 @@ export default class ARAgingSummaryService {
};
// Retrieve all overdue sale invoices.
const overdueSaleInvoices = await SaleInvoice.query()
.modify('dueInvoicesFromDate', filter.asDate)
.modify('overdueInvoicesFromDate', filter.asDate)
.onBuild(commonQuery);
// Retrieve all due sale invoices.
const currentInvoices = await SaleInvoice.query()
.modify('overdueInvoicesFromDate', filter.asDate)
.modify('dueInvoicesFromDate', filter.asDate)
.onBuild(commonQuery);
// AR aging summary report instance.
@@ -117,4 +118,21 @@ export default class ARAgingSummaryService {
meta: this.reportMetadata(tenantId),
};
}
/**
* Retrieves A/R aging summary in table format.
* @param {number} tenantId
* @param {IARAgingSummaryQuery} query
*/
async ARAgingSummaryTable(tenantId: number, query: IARAgingSummaryQuery) {
const report = await this.ARAgingSummary(tenantId, query);
const table = new ARAgingSummaryTable(report.data, query, {});
return {
columns: table.tableColumns(),
rows: table.tableRows(),
meta: report.meta,
query,
};
}
}

View File

@@ -1,4 +1,4 @@
import { groupBy, isEmpty, sum } from 'lodash';
import { Dictionary, groupBy, isEmpty, sum } from 'lodash';
import * as R from 'ramda';
import {
ICustomer,
@@ -54,7 +54,6 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
currentSaleInvoices,
'customerId'
);
// Initializes the aging periods.
this.agingPeriods = this.agingRangePeriods(
this.query.asDate,
@@ -189,7 +188,7 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
};
/**
* Retrieve AR aging summary report columns.
* Retrieve A/R aging summary report columns.
* @return {IARAgingSummaryColumns}
*/
public reportColumns(): IARAgingSummaryColumns {

View File

@@ -0,0 +1,38 @@
import {
IARAgingSummaryData,
IAgingSummaryData,
IAgingSummaryQuery,
ITableColumnAccessor,
ITableRow,
} from '@/interfaces';
import AgingSummaryTable from './AgingSummaryTable';
export default class ARAgingSummaryTable extends AgingSummaryTable {
readonly report: IARAgingSummaryData;
/**
* Constructor method.
* @param {IARAgingSummaryData} data
* @param {IAgingSummaryQuery} query
* @param {any} i18n
*/
constructor(data: IARAgingSummaryData, query: IAgingSummaryQuery, i18n: any) {
super(data, query, i18n);
}
/**
* Retrieves the contacts table rows.
* @returns {ITableRow[]}
*/
get contactsRows(): ITableRow[] {
return this.contactsNodes(this.report.customers);
}
/**
* Contact name node accessor.
* @returns {ITableColumnAccessor}
*/
get contactNameNodeAccessor(): ITableColumnAccessor {
return { key: 'customer_name', accessor: 'customerName' };
}
}

View File

@@ -0,0 +1,211 @@
import * as R from 'ramda';
import {
IAgingPeriod,
IAgingSummaryContact,
IAgingSummaryData,
IAgingSummaryQuery,
IAgingSummaryTotal,
ITableColumn,
ITableColumnAccessor,
ITableRow,
} from '@/interfaces';
import { tableRowMapper } from '@/utils';
import AgingReport from './AgingReport';
import { AgingSummaryRowType } from './_constants';
import { FinancialTable } from '../FinancialTable';
import { FinancialSheetStructure } from '../FinancialSheetStructure';
export default abstract class AgingSummaryTable extends R.compose(
FinancialSheetStructure,
FinancialTable
)(AgingReport) {
protected readonly report: IAgingSummaryData;
protected readonly query: IAgingSummaryQuery;
protected readonly agingPeriods: IAgingPeriod[];
protected readonly i18n: any;
/**
* Constructor method.
* @param {IARAgingSummaryData} data
* @param {IAgingSummaryQuery} query
* @param {any} i18n
*/
constructor(data: IAgingSummaryData, query: IAgingSummaryQuery, i18n: any) {
super();
this.report = data;
this.i18n = i18n;
this.query = query;
this.agingPeriods = this.agingRangePeriods(
this.query.asDate,
this.query.agingDaysBefore,
this.query.agingPeriods
);
}
// -------------------------
// # Accessors.
// -------------------------
/**
* Aging accessors of contact and total nodes.
* @param {IAgingSummaryContact | IAgingSummaryTotal} node
* @returns {ITableColumnAccessor[]}
*/
protected agingNodeAccessors = (
node: IAgingSummaryContact | IAgingSummaryTotal
): ITableColumnAccessor[] => {
return node.aging.map((aging, index) => ({
key: 'aging',
accessor: `aging[${index}].total.formattedAmount`,
}));
};
/**
* Contact name node accessor.
* @returns {ITableColumnAccessor}
*/
protected get contactNameNodeAccessor(): ITableColumnAccessor {
return { key: 'customer_name', accessor: 'customerName' };
}
/**
* Retrieves the common columns for all report nodes.
* @param {IAgingSummaryContact}
* @returns {ITableColumnAccessor[]}
*/
protected contactNodeAccessors = (
node: IAgingSummaryContact
): ITableColumnAccessor[] => {
return R.compose(
R.concat([
this.contactNameNodeAccessor,
{ key: 'current', accessor: 'current.formattedAmount' },
...this.agingNodeAccessors(node),
{ key: 'total', accessor: 'total.formattedAmount' },
])
)([]);
};
/**
* Retrieves the contact name table row.
* @param {IAgingSummaryContact} node -
* @return {ITableRow}
*/
protected contactNameNode = (node: IAgingSummaryContact): ITableRow => {
const columns = this.contactNodeAccessors(node);
const meta = {
rowTypes: [AgingSummaryRowType.Contact],
};
return tableRowMapper(node, columns, meta);
};
/**
* Maps the customers nodes to table rows.
* @param {IAgingSummaryContact[]} nodes
* @returns {ITableRow[]}
*/
protected contactsNodes = (nodes: IAgingSummaryContact[]): ITableRow[] => {
return nodes.map(this.contactNameNode);
};
/**
* Retrieves the common columns for all report nodes.
* @param {IAgingSummaryTotal}
* @returns {ITableColumnAccessor[]}
*/
protected totalNodeAccessors = (
node: IAgingSummaryTotal
): ITableColumnAccessor[] => {
return R.compose(
R.concat([
{ key: 'blank', value: '' },
{ key: 'current', accessor: 'current.formattedAmount' },
...this.agingNodeAccessors(node),
{ key: 'total', accessor: 'total.formattedAmount' },
])
)([]);
};
/**
* Retrieves the total row of the given report total node.
* @param {IAgingSummaryTotal} node
* @returns {ITableRow}
*/
protected totalNode = (node: IAgingSummaryTotal): ITableRow => {
const columns = this.totalNodeAccessors(node);
const meta = {
rowTypes: [AgingSummaryRowType.Total],
};
return tableRowMapper(node, columns, meta);
};
// -------------------------
// # Computed Rows.
// -------------------------
/**
* Retrieves the contacts table rows.
* @returns {ITableRow[]}
*/
protected get contactsRows(): ITableRow[] {
return [];
}
/**
* Table total row.
* @returns {ITableRow}
*/
protected get totalRow(): ITableRow {
return this.totalNode(this.report.total);
}
/**
* Retrieves the table rows.
* @returns {ITableRow[]}
*/
public tableRows = (): ITableRow[] => {
return R.compose(
R.unless(R.isEmpty, R.append(this.totalRow)),
R.concat(this.contactsRows)
)([]);
};
// -------------------------
// # Columns.
// -------------------------
/**
* Retrieves the aging table columns.
* @returns {ITableColumn[]}
*/
protected agingTableColumns = (): ITableColumn[] => {
return this.agingPeriods.map((agingPeriod) => {
return {
label: `${agingPeriod.beforeDays} - ${
agingPeriod.toDays || 'And Over'
}`,
key: 'aging_period',
};
});
};
/**
* Retrieves the contact name table column.
* @returns {ITableColumn}
*/
protected contactNameTableColumn = (): ITableColumn => {
return { label: 'Customer name', key: 'customer_name' };
};
/**
* Retrieves the report columns.
* @returns {ITableColumn}
*/
public tableColumns = (): ITableColumn[] => {
return R.compose(this.tableColumnsCellIndexing)([
this.contactNameTableColumn(),
{ label: 'Current', key: 'current' },
...this.agingTableColumns(),
{ label: 'Total', key: 'total' },
]);
};
}

View File

@@ -0,0 +1,4 @@
export enum AgingSummaryRowType {
Contact = 'contact',
Total = 'total',
}

View File

@@ -12,7 +12,7 @@ export const FinancialTable = (Base) =>
* @param {ITableColumn[]} columns
* @returns {ITableColumn[]}
*/
protected tableColumnsCellIndexing = (
public tableColumnsCellIndexing = (
columns: ITableColumn[]
): ITableColumn[] => {
const cellIndex = increment(-1);

View File

@@ -1,13 +1,13 @@
import { Service, Inject } from 'typedi';
import Knex from 'knex';
import { Knex } from 'knex';
import Bluebird from 'bluebird';
import { IVendorCreditAppliedBill } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import Bluebird from 'bluebird';
@Service()
export default class ApplyVendorCreditSyncBills {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Increment bills credited amount.
@@ -49,4 +49,4 @@ export default class ApplyVendorCreditSyncBills {
.findById(vendorCreditAppliedBill.billId)
.decrement('creditedAmount', vendorCreditAppliedBill.amount);
};
}
}

View File

@@ -18,11 +18,11 @@ function APAgingSummaryBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useAPAgingSummaryContext();
const { isAPAgingLoading } = useAPAgingSummaryContext();
return (
<FinancialReportBody>
{isLoading ? (
{isAPAgingLoading ? (
<FinancialSheetSkeleton />
) : (
<APAgingSummaryTable organizationName={organizationName} />

View File

@@ -20,7 +20,7 @@ export default function APAgingSummaryTable({
}) {
// AP aging summary report content.
const {
APAgingSummary: { tableRows },
APAgingSummary: { table },
isAPAgingLoading,
} = useAPAgingSummaryContext();
@@ -36,7 +36,7 @@ export default function APAgingSummaryTable({
>
<APAgingSummaryDataTable
columns={columns}
data={tableRows}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true}
sticky={true}
@@ -54,6 +54,24 @@ const APAgingSummaryDataTable = styled(ReportDataTable)`
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
&:not(.no-results) {
.td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
&:not(:first-child) .td {
border-top: 1px solid transparent;
}
&.row_type--total {
font-weight: 500;
.td {
border-top: 1px solid #bbb;
border-bottom: 3px double #333;
}
}
}
}
}
`;

View File

@@ -36,19 +36,23 @@ export const getAPAgingSummaryQuerySchema = () => {
.required()
.integer()
.positive()
.label('agingBeforeDays'),
.label('Aging days before')
.min(1)
.max(500),
agingPeriods: Yup.number()
.required()
.integer()
.positive()
.label('agingPeriods'),
.max(12)
.min(1)
.label('Aging periods'),
});
};
/**
* Parses the AP aging summary query state.
* @param locationQuery
* @returns
* @param locationQuery
* @returns
*/
const parseAPAgingSummaryQuery = (locationQuery) => {
const defaultQuery = getDefaultAPAgingSummaryQuery();

View File

@@ -1,57 +1,20 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import { If, FormattedMessage as T } from '@/components';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { getColumnWidth } from '@/utils';
import { If } from '@/components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
/**
* Retrieve AP aging summary columns.
*/
export const useAPAgingSummaryColumns = () => {
const {
APAgingSummary: { tableRows, columns },
APAgingSummary: { table },
} = useAPAgingSummaryContext();
const agingColumns = React.useMemo(() => {
return columns.map(
(agingColumn) =>
`${agingColumn.before_days} - ${
agingColumn.to_days || intl.get('and_over')
}`,
);
}, [columns]);
return useMemo(
() => [
{
Header: <T id={'vendor_name'} />,
accessor: 'name',
className: 'vendor_name',
width: 240,
sticky: 'left',
textOverview: true,
},
{
Header: <T id={'current'} />,
accessor: 'current',
className: 'current',
width: getColumnWidth(tableRows, `current`, { minWidth: 120 }),
},
...agingColumns.map((agingColumn, index) => ({
Header: agingColumn,
accessor: `aging-${index}`,
width: getColumnWidth(tableRows, `aging-${index}`, { minWidth: 120 }),
})),
{
Header: <T id={'total'} />,
accessor: 'total',
width: getColumnWidth(tableRows, 'total', { minWidth: 120 }),
},
],
[tableRows, agingColumns],
);
return agingSummaryDynamicColumns(table.columns, table.rows);
};
/**

View File

@@ -19,7 +19,10 @@ export default function ReceivableAgingSummaryTable({
organizationName,
}) {
// AR aging summary report context.
const { ARAgingSummary, isARAgingLoading } = useARAgingSummaryContext();
const {
ARAgingSummary: { table },
isARAgingLoading,
} = useARAgingSummaryContext();
// AR aging summary columns.
const columns = useARAgingSummaryColumns();
@@ -33,7 +36,7 @@ export default function ReceivableAgingSummaryTable({
>
<ARAgingSummaryDataTable
columns={columns}
data={ARAgingSummary.tableRows}
data={table.rows}
rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true}
sticky={true}

View File

@@ -35,12 +35,16 @@ export const getARAgingSummaryQuerySchema = () => {
.required()
.integer()
.positive()
.label('agingDaysBefore'),
.label('Aging days before')
.min(1)
.max(500),
agingPeriods: Yup.number()
.required()
.integer()
.positive()
.label('agingPeriods'),
.max(12)
.min(1)
.label('Aging periods'),
});
};

View File

@@ -1,71 +1,20 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
import { If, FormattedMessage as T } from '@/components';
import { getColumnWidth } from '@/utils';
import { Align } from '@/constants';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
/**
* Retrieve AR aging summary columns.
*/
export const useARAgingSummaryColumns = () => {
const {
ARAgingSummary: { tableRows, columns },
ARAgingSummary: { table },
} = useARAgingSummaryContext();
const agingColumns = React.useMemo(() => {
return columns.map(
(agingColumn) =>
`${agingColumn.before_days} - ${
agingColumn.to_days || intl.get('and_over')
}`,
);
}, [columns]);
return React.useMemo(
() => [
{
Header: <T id={'customer_name'} />,
accessor: 'name',
className: 'customer_name',
sticky: 'left',
width: 240,
textOverview: true,
},
{
Header: <T id={'current'} />,
accessor: 'current',
className: 'current',
width: getColumnWidth(tableRows, `current`, {
minWidth: 120,
}),
align: Align.Right
},
...agingColumns.map((agingColumn, index) => ({
Header: agingColumn,
accessor: `aging-${index}`,
width: getColumnWidth(tableRows, `aging-${index}`, {
minWidth: 120,
}),
align: Align.Right
})),
{
Header: <T id={'total'} />,
id: 'total',
accessor: 'total',
className: 'total',
width: getColumnWidth(tableRows, 'total', {
minWidth: 120,
}),
align: Align.Right
},
],
[tableRows, agingColumns],
);
return agingSummaryDynamicColumns(table.columns, table.rows);
};
/**

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import * as R from 'ramda';
import { getColumnWidth } from '@/utils';
import { Align } from '@/constants';
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const contactNameAccessor = R.curry((data, column) => ({
key: column.key,
Header: column.label,
accessor: getTableCellValueAccessor(column.cell_index),
sticky: 'left',
width: 240,
textOverview: true,
}));
const currentAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
key: column.key,
Header: column.label,
accessor,
className: column.id,
width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right,
};
});
const totalAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
Header: column.label,
id: column.key,
accessor: getTableCellValueAccessor(column.cell_index),
className: column.key,
width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right,
};
});
const agingPeriodAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
Header: column.label,
id: `${column.key}-${column.cell_index}`,
accessor,
className: column.key,
width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right,
};
});
const dynamicColumnMapper = R.curry((data, column) => {
const totalAccessorColumn = totalAccessor(data);
const currentAccessorColumn = currentAccessor(data);
const customerNameAccessorColumn = contactNameAccessor(data);
const agingPeriodAccessorColumn = agingPeriodAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'total'), totalAccessorColumn),
R.when(R.pathEq(['key'], 'current'), currentAccessorColumn),
R.when(R.pathEq(['key'], 'customer_name'), customerNameAccessorColumn),
R.when(R.pathEq(['key'], 'vendor_name'), customerNameAccessorColumn),
R.when(R.pathEq(['key'], 'aging_period'), agingPeriodAccessorColumn),
)(column);
});
export const agingSummaryDynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};

View File

@@ -138,26 +138,12 @@ export function useARAgingSummaryReport(query, props) {
method: 'get',
url: '/financial_statements/receivable_aging_summary',
params: query,
headers: {
Accept: 'application/json+table',
},
},
{
select: (res) => ({
columns: res.data.columns,
data: res.data.data,
query: res.data.query,
tableRows: ARAgingSummaryTableRowsMapper({
customers: res.data.data.customers,
total: res.data.data.total,
columns: res.data.columns,
}),
}),
defaultData: {
data: {
customers: [],
total: {},
},
columns: [],
tableRows: [],
},
select: (res) => res.data,
...props,
},
);
@@ -173,26 +159,12 @@ export function useAPAgingSummaryReport(query, props) {
method: 'get',
url: '/financial_statements/payable_aging_summary',
params: query,
headers: {
Accept: 'application/json+table',
},
},
{
select: (res) => ({
columns: res.data.columns,
data: res.data.data,
query: res.data.query,
tableRows: APAgingSummaryTableRowsMapper({
vendors: res.data.data.vendors,
total: res.data.data.total,
columns: res.data.columns,
}),
}),
defaultData: {
data: {
vendors: [],
total: {},
},
columns: [],
tableRows: [],
},
select: (res) => res.data,
...props,
},
);