mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
Merge pull request #229 from bigcapitalhq/abouhuolia/big-45-receivablepayable-again-report-issue
fix AP/AR aging summary issue
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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' };
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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' };
|
||||
}
|
||||
}
|
||||
@@ -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' },
|
||||
]);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum AgingSummaryRowType {
|
||||
Contact = 'contact',
|
||||
Total = 'total',
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const FinancialTable = (Base) =>
|
||||
* @param {ITableColumn[]} columns
|
||||
* @returns {ITableColumn[]}
|
||||
*/
|
||||
protected tableColumnsCellIndexing = (
|
||||
public tableColumnsCellIndexing = (
|
||||
columns: ITableColumn[]
|
||||
): ITableColumn[] => {
|
||||
const cellIndex = increment(-1);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ function APAgingSummaryBodyJSX({
|
||||
// #withCurrentOrganization
|
||||
organizationName,
|
||||
}) {
|
||||
const { isLoading } = useAPAgingSummaryContext();
|
||||
const { isAPAgingLoading } = useAPAgingSummaryContext();
|
||||
|
||||
return (
|
||||
<FinancialReportBody>
|
||||
{isLoading ? (
|
||||
{isAPAgingLoading ? (
|
||||
<FinancialSheetSkeleton />
|
||||
) : (
|
||||
<APAgingSummaryTable organizationName={organizationName} />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user