Compare commits

..

17 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
7f746b96c8 chore: remove /data directory from git ignored dirs 2023-08-29 03:05:19 +02:00
Ahmed Bouhuolia
ed2bca6b74 chore: dump CHANGELOG for 0.9.12 2023-08-29 02:54:03 +02:00
Ahmed Bouhuolia
f9d5a3c69a Merge pull request #231 from bigcapitalhq/fix-filter-transactions-date-format
fix(server): date format of filtering transactions by date range
2023-08-29 02:42:35 +02:00
Ahmed Bouhuolia
84445d4bac fix(server): date format of filtering transactions by date range 2023-08-29 02:41:40 +02:00
Ahmed Bouhuolia
75d8864aae Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2023-08-28 21:03:23 +02:00
Ahmed Bouhuolia
cb6dab08d8 chore: remove un-used methods 2023-08-28 21:01:34 +02:00
Ahmed Bouhuolia
5112ef9b64 Merge pull request #230 from bigcapitalhq/abouhuolia/big-60-fromto-date-is-not-showing-up-on-inventory-items-details
fix: change the default from/date date value of reports
2023-08-28 20:59:02 +02:00
Ahmed Bouhuolia
a630e8a612 fix: change the default from/to dates of customers/vendors transactions 2023-08-28 20:53:52 +02:00
Ahmed Bouhuolia
4df63561cf fix(webapp): change the default from/to date values of reports 2023-08-27 16:00:54 +02:00
Ahmed Bouhuolia
c7a3bac44c fix(server): change the default from/date date value of reports 2023-08-27 15:50:52 +02:00
Ahmed Bouhuolia
251c54be60 Merge pull request #229 from bigcapitalhq/abouhuolia/big-45-receivablepayable-again-report-issue
fix AP/AR aging summary issue
2023-08-27 00:58:17 +02:00
Ahmed Bouhuolia
5dec4a7df0 chore(server): document methods 2023-08-27 00:56:14 +02:00
Ahmed Bouhuolia
0d57ca88bf fix(server): avoid return total row on aging summary reports if no customers 2023-08-27 00:45:12 +02:00
Ahmed Bouhuolia
b9be83dc2b fix(webapp): add validation to aging summary customize form 2023-08-27 00:44:37 +02:00
Ahmed Bouhuolia
321de4d327 refactor(webapp): AR/AP aging summary table columns 2023-08-26 02:16:35 +02:00
Ahmed Bouhuolia
4e66d1ac98 feat(server): AP/AR aging summary table transformer 2023-08-24 23:24:05 +02:00
Ahmed Bouhuolia
b5fe5a8bcb Merge pull request #227 from bigcapitalhq/fix-spelling-a-char
Fix spelling words start with `A` letter
2023-08-22 22:56:23 +02:00
69 changed files with 786 additions and 361 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,9 @@
node_modules/ node_modules/
data
# Docker volumes data directory
/data
# Production env file
.env .env
test-results/ test-results/

View File

@@ -2,6 +2,20 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
# [0.9.12] - 29-08-2023
* Refactor: split the services to multiple service classes. (by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/202)
* Fix: create quick customer/vendor by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/206
* Fix: typo in bill success message without bill number by @KalliopiPliogka in https://github.com/bigcapitalhq/bigcapital/pull/219
* Fix: AP/AR aging summary issue by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/229
* Fix: shouldn't write GL entries when save transaction as draft. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/221
* Fix: Transaction type of credit note and vendor credit are not defined on account transactions by @abouolia in
* Fix: date format of filtering transactions by date range by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/231
* Fix: change the default from/date date value of reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/230
* Fix: typos in words start with `A` letter by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/227
* Fix: filter by customers, vendors and items in reports do not work by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/224
https://github.com/bigcapitalhq/bigcapital/pull/225
# [0.9.11] - 23-07-2023 # [0.9.11] - 23-07-2023
* added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198 * added: Restart policy to docker compose files. by @suhaibaffan in https://github.com/bigcapitalhq/bigcapital/pull/198

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
export const TransactionTypes = {
SaleInvoice: 'Sale invoice',
SaleReceipt: 'Sale receipt',
PaymentReceive: 'Payment receive',
Bill: 'Bill',
BillPayment: 'Payment made',
VendorOpeningBalance: 'Vendor opening balance',
CustomerOpeningBalance: 'Customer opening balance',
InventoryAdjustment: 'Inventory adjustment',
ManualJournal: 'Manual journal',
Journal: 'Manual journal',
Expense: 'Expense',
OwnerContribution: 'Owner contribution',
TransferToAccount: 'Transfer to account',
TransferFromAccount: 'Transfer from account',
OtherIncome: 'Other income',
OtherExpense: 'Other expense',
OwnerDrawing: 'Owner drawing',
InvoiceWriteOff: 'Invoice write-off',
CreditNote: 'transaction_type.credit_note',
VendorCredit: 'transaction_type.vendor_credit',
RefundCreditNote: 'transaction_type.refund_credit_note',
RefundVendorCredit: 'transaction_type.refund_vendor_credit',
LandedCost: 'transaction_type.landed_cost',
};

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import { INumberFormatQuery } from './FinancialStatements';
export interface IAgingPeriodTotal extends IAgingPeriod { export interface IAgingPeriodTotal extends IAgingPeriod {
total: IAgingAmount; total: IAgingAmount;
}; }
export interface IAgingAmount { export interface IAgingAmount {
amount: number; amount: number;
@@ -20,3 +23,22 @@ export interface IAgingSummaryContact {
aging: IAgingPeriodTotal[]; aging: IAgingPeriodTotal[];
total: IAgingAmount; 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

@@ -59,15 +59,9 @@ export default class AccountTransaction extends TenantModel {
} }
}, },
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(query, startDate, endDate, type = 'day') {
const dateFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate) const fromDate = moment(startDate).startOf(type).format(dateFormat);
.utcOffset(0) const toDate = moment(endDate).endOf(type).format(dateFormat);
.startOf(type)
.format(dateFormat);
const toDate = moment(endDate)
.utcOffset(0)
.endOf(type)
.format(dateFormat);
if (startDate) { if (startDate) {
query.where('date', '>=', fromDate); query.where('date', '>=', fromDate);
@@ -111,7 +105,6 @@ export default class AccountTransaction extends TenantModel {
query.modify('filterDateRange', null, toDate); query.modify('filterDateRange', null, toDate);
query.modify('sumationCreditDebit'); query.modify('sumationCreditDebit');
}, },
contactsOpeningBalance( contactsOpeningBalance(
query, query,
openingDate, openingDate,

View File

@@ -33,7 +33,7 @@ export default class InventoryCostLotTracker extends TenantModel {
query.groupBy('item_id'); query.groupBy('item_id');
}, },
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(query, startDate, endDate, type = 'day') {
const dateFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat); const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat);

View File

@@ -36,7 +36,7 @@ export default class InventoryTransaction extends TenantModel {
static get modifiers() { static get modifiers() {
return { return {
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(query, startDate, endDate, type = 'day') {
const dateFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat); const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat);

View File

@@ -176,7 +176,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
* Filters the invoices between the given date range. * Filters the invoices between the given date range.
*/ */
filterDateRange(query, startDate, endDate, type = 'day') { filterDateRange(query, startDate, endDate, type = 'day') {
const dateFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat); const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat);

View File

@@ -1,17 +1,12 @@
import { Service, Inject } from 'typedi'; import { Service } from 'typedi';
import { includes, difference, camelCase, upperFirst } from 'lodash'; import { includes, camelCase, upperFirst } from 'lodash';
import { ACCOUNT_TYPE } from '@/data/AccountTypes'; import { IAccount } from '@/interfaces';
import { IAccount, ICashflowTransactionLine } from '@/interfaces';
import { getCashflowTransactionType } from './utils'; import { getCashflowTransactionType } from './utils';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants'; import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service() @Service()
export class CommandCashflowValidator { export class CommandCashflowValidator {
@Inject()
private tenancy: HasTenancyService;
/** /**
* Validates the lines accounts type should be cash or bank account. * Validates the lines accounts type should be cash or bank account.
* @param {IAccount} accounts - * @param {IAccount} accounts -

View File

@@ -1,14 +0,0 @@
import { difference, includes } from 'lodash';
import { ICashflowTransactionLine } from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { Inject, Service } from 'typedi';
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
import { IAccount } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class CommandCashflowTransaction {
@Inject()
private tenancy: HasTenancyService;
}

View File

@@ -5,6 +5,7 @@ import TenancyService from '@/services/Tenancy/TenancyService';
import APAgingSummarySheet from './APAgingSummarySheet'; import APAgingSummarySheet from './APAgingSummarySheet';
import { Tenant } from '@/system/models'; import { Tenant } from '@/system/models';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import APAgingSummaryTable from './APAgingSummaryTable';
@Service() @Service()
export default class PayableAgingSummaryService { export default class PayableAgingSummaryService {
@@ -84,7 +85,7 @@ export default class PayableAgingSummaryService {
// Common query. // Common query.
const commonQuery = (query) => { const commonQuery = (query) => {
if (isEmpty(filter.branchesIds)) { if (!isEmpty(filter.branchesIds)) {
query.modify('filterByBranches', filter.branchesIds); query.modify('filterByBranches', filter.branchesIds);
} }
}; };
@@ -118,4 +119,21 @@ export default class PayableAgingSummaryService {
meta: this.reportMetadata(tenantId), 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 { return {
vendorName: vendor.displayName, vendorName: vendor.displayName,
current: this.formatTotalAmount(currentTotal), current: this.formatAmount(currentTotal),
aging: agingPeriods, aging: agingPeriods,
total: this.formatTotalAmount(amount), 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 TenancyService from '@/services/Tenancy/TenancyService';
import ARAgingSummarySheet from './ARAgingSummarySheet'; import ARAgingSummarySheet from './ARAgingSummarySheet';
import { Tenant } from '@/system/models'; import { Tenant } from '@/system/models';
import ARAgingSummaryTable from './ARAgingSummaryTable';
@Service() @Service()
export default class ARAgingSummaryService { export default class ARAgingSummaryService {
@@ -89,12 +90,12 @@ export default class ARAgingSummaryService {
}; };
// Retrieve all overdue sale invoices. // Retrieve all overdue sale invoices.
const overdueSaleInvoices = await SaleInvoice.query() const overdueSaleInvoices = await SaleInvoice.query()
.modify('dueInvoicesFromDate', filter.asDate) .modify('overdueInvoicesFromDate', filter.asDate)
.onBuild(commonQuery); .onBuild(commonQuery);
// Retrieve all due sale invoices. // Retrieve all due sale invoices.
const currentInvoices = await SaleInvoice.query() const currentInvoices = await SaleInvoice.query()
.modify('overdueInvoicesFromDate', filter.asDate) .modify('dueInvoicesFromDate', filter.asDate)
.onBuild(commonQuery); .onBuild(commonQuery);
// AR aging summary report instance. // AR aging summary report instance.
@@ -117,4 +118,21 @@ export default class ARAgingSummaryService {
meta: this.reportMetadata(tenantId), 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 * as R from 'ramda';
import { import {
ICustomer, ICustomer,
@@ -54,7 +54,6 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
currentSaleInvoices, currentSaleInvoices,
'customerId' 'customerId'
); );
// Initializes the aging periods. // Initializes the aging periods.
this.agingPeriods = this.agingRangePeriods( this.agingPeriods = this.agingRangePeriods(
this.query.asDate, 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} * @return {IARAgingSummaryColumns}
*/ */
public reportColumns(): 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

@@ -37,7 +37,7 @@ export default class BalanceSheetStatementService
displayColumnsBy: 'month', displayColumnsBy: 'month',
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
precision: 2, precision: 2,

View File

@@ -40,7 +40,7 @@ export default class CashFlowStatementService
displayColumnsType: 'total', displayColumnsType: 'total',
displayColumnsBy: 'day', displayColumnsBy: 'day',
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
precision: 2, precision: 2,
divideOn1000: false, divideOn1000: false,

View File

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

View File

@@ -31,8 +31,8 @@ export default class GeneralLedgerService {
*/ */
get defaultQuery() { get defaultQuery() {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'cash', basis: 'cash',
numberFormat: { numberFormat: {
noCents: false, noCents: false,

View File

@@ -16,13 +16,13 @@ import { Tenant } from '@/system/models';
@Service() @Service()
export default class InventoryDetailsService extends FinancialSheet { export default class InventoryDetailsService extends FinancialSheet {
@Inject() @Inject()
tenancy: TenancyService; private tenancy: TenancyService;
@Inject() @Inject()
reportRepo: InventoryDetailsRepository; private reportRepo: InventoryDetailsRepository;
@Inject() @Inject()
inventoryService: InventoryService; private inventoryService: InventoryService;
/** /**
* Defaults balance sheet filter query. * Defaults balance sheet filter query.
@@ -30,8 +30,8 @@ export default class InventoryDetailsService extends FinancialSheet {
*/ */
private get defaultQuery(): IInventoryDetailsQuery { private get defaultQuery(): IInventoryDetailsQuery {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
itemsIds: [], itemsIds: [],
numberFormat: { numberFormat: {
precision: 2, precision: 2,

View File

@@ -27,7 +27,7 @@ export default class InventoryValuationSheetService {
*/ */
get defaultQuery(): IInventoryValuationReportQuery { get defaultQuery(): IInventoryValuationReportQuery {
return { return {
asDate: moment().endOf('year').format('YYYY-MM-DD'), asDate: moment().format('YYYY-MM-DD'),
itemsIds: [], itemsIds: [],
numberFormat: { numberFormat: {
precision: 2, precision: 2,

View File

@@ -25,8 +25,8 @@ export default class JournalSheetService {
*/ */
get defaultQuery() { get defaultQuery() {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
fromRange: null, fromRange: null,
toRange: null, toRange: null,
accountsIds: [], accountsIds: [],

View File

@@ -8,7 +8,7 @@ import { IProfitLossSheetQuery } from '@/interfaces';
*/ */
export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({ export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
divideOn1000: false, divideOn1000: false,

View File

@@ -12,10 +12,7 @@ import { Tenant } from '@/system/models';
@Service() @Service()
export default class InventoryValuationReportService { export default class InventoryValuationReportService {
@Inject() @Inject()
tenancy: TenancyService; private tenancy: TenancyService;
@Inject('logger')
logger: any;
/** /**
* Defaults balance sheet filter query. * Defaults balance sheet filter query.
@@ -23,8 +20,8 @@ export default class InventoryValuationReportService {
*/ */
get defaultQuery(): IInventoryValuationReportQuery { get defaultQuery(): IInventoryValuationReportQuery {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
itemsIds: [], itemsIds: [],
numberFormat: { numberFormat: {
precision: 2, precision: 2,
@@ -73,9 +70,9 @@ export default class InventoryValuationReportService {
tenantId: number, tenantId: number,
query: IInventoryValuationReportQuery query: IInventoryValuationReportQuery
): Promise<{ ): Promise<{
data: IInventoryValuationStatement, data: IInventoryValuationStatement;
query: IInventoryValuationReportQuery, query: IInventoryValuationReportQuery;
meta: IInventoryValuationSheetMeta, meta: IInventoryValuationSheetMeta;
}> { }> {
const { Item, InventoryTransaction } = this.tenancy.models(tenantId); const { Item, InventoryTransaction } = this.tenancy.models(tenantId);
@@ -87,7 +84,7 @@ export default class InventoryValuationReportService {
...this.defaultQuery, ...this.defaultQuery,
...query, ...query,
}; };
const inventoryItems = await Item.query().onBuild(q => { const inventoryItems = await Item.query().onBuild((q) => {
q.where('type', 'inventory'); q.where('type', 'inventory');
if (filter.itemsIds.length > 0) { if (filter.itemsIds.length > 0) {
@@ -106,7 +103,7 @@ export default class InventoryValuationReportService {
builder.whereIn('itemId', inventoryItemsIds); builder.whereIn('itemId', inventoryItemsIds);
// Filter the date range of the sheet. // Filter the date range of the sheet.
builder.modify('filterDateRange', filter.fromDate, filter.toDate) builder.modify('filterDateRange', filter.fromDate, filter.toDate);
} }
); );

View File

@@ -23,8 +23,8 @@ export default class SalesByItemsReportService {
*/ */
get defaultQuery(): ISalesByItemsReportQuery { get defaultQuery(): ISalesByItemsReportQuery {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
itemsIds: [], itemsIds: [],
numberFormat: { numberFormat: {
precision: 2, precision: 2,

View File

@@ -31,8 +31,8 @@ export default class TransactionsByCustomersService
*/ */
get defaultQuery(): ITransactionsByCustomersFilter { get defaultQuery(): ITransactionsByCustomersFilter {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
precision: 2, precision: 2,
divideOn1000: false, divideOn1000: false,

View File

@@ -32,7 +32,7 @@ export default class TransactionsByVendorsService
*/ */
get defaultQuery(): ITransactionsByVendorsFilter { get defaultQuery(): ITransactionsByVendorsFilter {
return { return {
fromDate: moment().format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
precision: 2, precision: 2,

View File

@@ -27,7 +27,7 @@ export default class TrialBalanceSheetService extends FinancialSheet {
get defaultQuery(): ITrialBalanceSheetQuery { get defaultQuery(): ITrialBalanceSheetQuery {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
numberFormat: { numberFormat: {
divideOn1000: false, divideOn1000: false,
negativeFormat: 'mines', negativeFormat: 'mines',

View File

@@ -9,13 +9,9 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork'; import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
import { ItemsValidators } from './ItemValidators';
@Service() @Service()
export class DeleteItem { export class DeleteItem {
@Inject()
private validators: ItemsValidators;
@Inject() @Inject()
private tenancy: HasTenancyService; private tenancy: HasTenancyService;

View File

@@ -1,5 +0,0 @@
export default class ItemsCostService {
}

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ export default function APAgingSummaryTable({
}) { }) {
// AP aging summary report content. // AP aging summary report content.
const { const {
APAgingSummary: { tableRows }, APAgingSummary: { table, query },
isAPAgingLoading, isAPAgingLoading,
} = useAPAgingSummaryContext(); } = useAPAgingSummaryContext();
@@ -31,12 +31,12 @@ export default function APAgingSummaryTable({
<FinancialSheet <FinancialSheet
companyName={organizationName} companyName={organizationName}
sheetType={intl.get('payable_aging_summary')} sheetType={intl.get('payable_aging_summary')}
asDate={new Date()} asDate={query.as_date}
loading={isAPAgingLoading} loading={isAPAgingLoading}
> >
<APAgingSummaryDataTable <APAgingSummaryDataTable
columns={columns} columns={columns}
data={tableRows} data={table.rows}
rowClassNames={tableRowTypesToClassnames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
@@ -54,6 +54,24 @@ const APAgingSummaryDataTable = styled(ReportDataTable)`
padding-top: 0.32rem; padding-top: 0.32rem;
padding-bottom: 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,12 +36,16 @@ export const getAPAgingSummaryQuerySchema = () => {
.required() .required()
.integer() .integer()
.positive() .positive()
.label('agingBeforeDays'), .label('Aging days before')
.min(1)
.max(500),
agingPeriods: Yup.number() agingPeriods: Yup.number()
.required() .required()
.integer() .integer()
.positive() .positive()
.label('agingPeriods'), .max(12)
.min(1)
.label('Aging periods'),
}); });
}; };

View File

@@ -1,57 +1,20 @@
// @ts-nocheck // @ts-nocheck
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import { If, FormattedMessage as T } from '@/components'; import { If } from '@/components';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { getColumnWidth } from '@/utils';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
/** /**
* Retrieve AP aging summary columns. * Retrieve AP aging summary columns.
*/ */
export const useAPAgingSummaryColumns = () => { export const useAPAgingSummaryColumns = () => {
const { const {
APAgingSummary: { tableRows, columns }, APAgingSummary: { table },
} = useAPAgingSummaryContext(); } = useAPAgingSummaryContext();
const agingColumns = React.useMemo(() => { return agingSummaryDynamicColumns(table.columns, table.rows);
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],
);
}; };
/** /**

View File

@@ -11,7 +11,10 @@ const ARAgingSummaryContext = createContext();
*/ */
function ARAgingSummaryProvider({ filter, ...props }) { function ARAgingSummaryProvider({ filter, ...props }) {
// Transformes the filter from to the url query. // Transformes the filter from to the url query.
const query = useMemo(() => transformFilterFormToQuery(filter), [filter]); const requestQuery = useMemo(
() => transformFilterFormToQuery(filter),
[filter],
);
// A/R aging summary sheet context. // A/R aging summary sheet context.
const { const {
@@ -19,11 +22,10 @@ function ARAgingSummaryProvider({ filter, ...props }) {
isLoading: isARAgingLoading, isLoading: isARAgingLoading,
isFetching: isARAgingFetching, isFetching: isARAgingFetching,
refetch, refetch,
} = useARAgingSummaryReport(query, { keepPreviousData: true }); } = useARAgingSummaryReport(requestQuery, { keepPreviousData: true });
const provider = { const provider = {
ARAgingSummary, ARAgingSummary,
isARAgingLoading, isARAgingLoading,
isARAgingFetching, isARAgingFetching,
refetch, refetch,

View File

@@ -19,7 +19,10 @@ export default function ReceivableAgingSummaryTable({
organizationName, organizationName,
}) { }) {
// AR aging summary report context. // AR aging summary report context.
const { ARAgingSummary, isARAgingLoading } = useARAgingSummaryContext(); const {
ARAgingSummary: { table, query },
isARAgingLoading,
} = useARAgingSummaryContext();
// AR aging summary columns. // AR aging summary columns.
const columns = useARAgingSummaryColumns(); const columns = useARAgingSummaryColumns();
@@ -28,12 +31,12 @@ export default function ReceivableAgingSummaryTable({
<FinancialSheet <FinancialSheet
companyName={organizationName} companyName={organizationName}
sheetType={intl.get('receivable_aging_summary')} sheetType={intl.get('receivable_aging_summary')}
asDate={new Date()} asDate={query.as_date}
loading={isARAgingLoading} loading={isARAgingLoading}
> >
<ARAgingSummaryDataTable <ARAgingSummaryDataTable
columns={columns} columns={columns}
data={ARAgingSummary.tableRows} data={table.rows}
rowClassNames={tableRowTypesToClassnames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}

View File

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

View File

@@ -1,71 +1,20 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider'; import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
import { If, FormattedMessage as T } from '@/components'; import { If, FormattedMessage as T } from '@/components';
import { getColumnWidth } from '@/utils';
import { Align } from '@/constants';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { agingSummaryDynamicColumns } from '../AgingSummary/dynamicColumns';
/** /**
* Retrieve AR aging summary columns. * Retrieve AR aging summary columns.
*/ */
export const useARAgingSummaryColumns = () => { export const useARAgingSummaryColumns = () => {
const { const {
ARAgingSummary: { tableRows, columns }, ARAgingSummary: { table },
} = useARAgingSummaryContext(); } = useARAgingSummaryContext();
const agingColumns = React.useMemo(() => { return agingSummaryDynamicColumns(table.columns, table.rows);
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],
);
}; };
/** /**

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

@@ -15,7 +15,7 @@ import { useAppQueryString } from '@/hooks';
*/ */
export const getDefaultBalanceSheetQuery = () => ({ export const getDefaultBalanceSheetQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'cash', basis: 'cash',
displayColumnsType: 'total', displayColumnsType: 'total',
filterByOption: 'without-zero-balance', filterByOption: 'without-zero-balance',

View File

@@ -12,7 +12,7 @@ import { useAppQueryString } from '@/hooks';
export const getDefaultCashFlowSheetQuery = () => { export const getDefaultCashFlowSheetQuery = () => {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'cash', basis: 'cash',
displayColumnsType: 'total', displayColumnsType: 'total',
filterByOption: 'with-transactions', filterByOption: 'with-transactions',

View File

@@ -18,7 +18,7 @@ export default function CustomersBalanceSummaryTable({
companyName, companyName,
}) { }) {
const { const {
CustomerBalanceSummary: { table }, CustomerBalanceSummary: { table, query },
} = useCustomersBalanceSummaryContext(); } = useCustomersBalanceSummaryContext();
// Retrieves the customers summary columns. // Retrieves the customers summary columns.
@@ -28,7 +28,7 @@ export default function CustomersBalanceSummaryTable({
<FinancialSheet <FinancialSheet
companyName={companyName} companyName={companyName}
sheetType={intl.get('customers_balance_summary')} sheetType={intl.get('customers_balance_summary')}
asDate={new Date()} asDate={query.as_date}
> >
<CustomerBalanceDataTable <CustomerBalanceDataTable
columns={columns} columns={columns}

View File

@@ -13,6 +13,7 @@ import { CustomersTransactionsBody } from './CustomersTransactionsBody';
import { CustomersTransactionsProvider } from './CustomersTransactionsProvider'; import { CustomersTransactionsProvider } from './CustomersTransactionsProvider';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { useCustomersTransactionsQuery } from './_utils';
/** /**
* Customers transactions. * Customers transactions.
@@ -22,11 +23,7 @@ function CustomersTransactions({
toggleCustomersTransactionsFilterDrawer, toggleCustomersTransactionsFilterDrawer,
}) { }) {
// filter // filter
const [filter, setFilter] = useState({ const [filter, setFilter] = useCustomersTransactionsQuery();
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
const handleFilterSubmit = (filter) => { const handleFilterSubmit = (filter) => {
const _filter = { const _filter = {

View File

@@ -14,7 +14,7 @@ function CustomersTransactionsProvider({ filter, ...props }) {
filter, filter,
]); ]);
// fetches the customers transactions. // Fetches the customers transactions.
const { const {
data: customersTransactions, data: customersTransactions,
isFetching: isCustomersTransactionsFetching, isFetching: isCustomersTransactionsFetching,

View File

@@ -1,6 +1,11 @@
// @ts-nocheck
import * as Yup from 'yup'; import * as Yup from 'yup';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import moment from 'moment'; import moment from 'moment';
import { transformToForm } from '@/utils';
import { castArray } from 'lodash';
import { useMemo } from 'react';
import { useAppQueryString } from '@/hooks';
export const getCustomersTransactionsQuerySchema = () => { export const getCustomersTransactionsQuerySchema = () => {
return Yup.object().shape({ return Yup.object().shape({
@@ -13,7 +18,31 @@ export const getCustomersTransactionsQuerySchema = () => {
}; };
export const getCustomersTransactionsDefaultQuery = () => ({ export const getCustomersTransactionsDefaultQuery = () => ({
fromDate: moment().toDate(), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().toDate(), toDate: moment().format('YYYY-MM-DD'),
customersIds: [], customersIds: [],
filterByOption: 'with-transactions',
}); });
const parseCustomersTransactionsQuery = (query) => {
const defaultQuery = getCustomersTransactionsDefaultQuery();
const transformedQuery = {
...defaultQuery,
...transformToForm(query, defaultQuery),
};
return {
...transformedQuery,
customersIds: castArray(transformedQuery.customersIds),
};
};
export const useCustomersTransactionsQuery = () => {
const [locationQuery, setLocationQuery] = useAppQueryString();
const query = useMemo(
() => parseCustomersTransactionsQuery(locationQuery),
[locationQuery],
);
return [query, setLocationQuery];
};

View File

@@ -28,8 +28,8 @@ export const filterAccountsOptions = [
* Retrieves the default general ledger query. * Retrieves the default general ledger query.
*/ */
export const getDefaultGeneralLedgerQuery = () => ({ export const getDefaultGeneralLedgerQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'accrual', basis: 'accrual',
filterByOption: 'with-transactions', filterByOption: 'with-transactions',
branchesIds: [], branchesIds: [],

View File

@@ -35,8 +35,8 @@ export function InventoryItemDetailsTable({
companyName={companyName} companyName={companyName}
sheetType={intl.get('inventory_item_details')} sheetType={intl.get('inventory_item_details')}
loading={isInventoryItemDetailsLoading} loading={isInventoryItemDetailsLoading}
fromDate={query.from_date} fromDate={query.fromDate}
toDate={query.to_date} toDate={query.toDate}
fullWidth={true} fullWidth={true}
> >
<InventoryItemDetailsDataTable <InventoryItemDetailsDataTable

View File

@@ -31,9 +31,6 @@ const numericColumn = R.curry((data, index, column) => ({
align: Align.Right, align: Align.Right,
})); }));
/**
* columns mapper.
*/
const columnsMapper = R.curry((data, index, column) => ({ const columnsMapper = R.curry((data, index, column) => ({
id: column.key, id: column.key,
key: column.key, key: column.key,

View File

@@ -12,8 +12,8 @@ import { transformToForm } from '@/utils';
* Retrieves inventory item details default query. * Retrieves inventory item details default query.
*/ */
export const getInventoryItemDetailsDefaultQuery = () => ({ export const getInventoryItemDetailsDefaultQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
itemsIds: [], itemsIds: [],
warehousesIds: [], warehousesIds: [],
branchesIds: [], branchesIds: [],

View File

@@ -19,7 +19,7 @@ export default function InventoryValuationTable({
}) { }) {
// inventory valuation context. // inventory valuation context.
const { const {
inventoryValuation: { tableRows }, inventoryValuation: { tableRows, query },
isLoading, isLoading,
} = useInventoryValuationContext(); } = useInventoryValuationContext();
@@ -30,7 +30,7 @@ export default function InventoryValuationTable({
<InventoryValuationSheet <InventoryValuationSheet
companyName={companyName} companyName={companyName}
sheetType={intl.get('inventory_valuation')} sheetType={intl.get('inventory_valuation')}
asDate={new Date()} asDate={query.as_date}
loading={isLoading} loading={isLoading}
> >
<InventoryValuationDataTable <InventoryValuationDataTable

View File

@@ -20,7 +20,7 @@ export const getInventoryValuationQuerySchema = () => {
* Retrieves the inventory valuation sheet default query. * Retrieves the inventory valuation sheet default query.
*/ */
export const getInventoryValuationQuery = () => ({ export const getInventoryValuationQuery = () => ({
asDate: moment().endOf('day').format('YYYY-MM-DD'), asDate: moment().format('YYYY-MM-DD'),
filterByOption: 'with-transactions', filterByOption: 'with-transactions',
itemsIds: [], itemsIds: [],
branchesIds: [], branchesIds: [],

View File

@@ -11,17 +11,16 @@ import { transformToForm } from '@/utils';
*/ */
export const getDefaultJournalQuery = () => { export const getDefaultJournalQuery = () => {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'accrual', basis: 'accrual',
}; };
}; };
/** /**
* Parses balance sheet query. * Parses balance sheet query.
*/ */
const parseJournalQuery = (locationQuery) => { const parseJournalQuery = (locationQuery) => {
const defaultQuery = getDefaultJournalQuery(); const defaultQuery = getDefaultJournalQuery();
return { return {

View File

@@ -23,7 +23,7 @@ export default function ProfitLossSheetTable({
} = useProfitLossSheetContext(); } = useProfitLossSheetContext();
// Retrieves the profit/loss table columns. // Retrieves the profit/loss table columns.
const tableColumns = useProfitLossSheetColumns(); const columns = useProfitLossSheetColumns();
// Retrieve default expanded rows of balance sheet. // Retrieve default expanded rows of balance sheet.
const expandedRows = React.useMemo( const expandedRows = React.useMemo(
@@ -40,7 +40,7 @@ export default function ProfitLossSheetTable({
basis={query.basis} basis={query.basis}
> >
<ProfitLossDataTable <ProfitLossDataTable
columns={tableColumns} columns={columns}
data={table.rows} data={table.rows}
noInitialFetch={true} noInitialFetch={true}
expanded={expandedRows} expanded={expandedRows}

View File

@@ -16,7 +16,7 @@ import { castArray } from 'lodash';
export const getDefaultProfitLossQuery = () => ({ export const getDefaultProfitLossQuery = () => ({
basis: 'cash', basis: 'cash',
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
displayColumnsType: 'total', displayColumnsType: 'total',
filterByOption: 'with-transactions', filterByOption: 'with-transactions',

View File

@@ -11,8 +11,8 @@ import { castArray } from 'lodash';
* Retrieves the purchases by items query. * Retrieves the purchases by items query.
*/ */
export const getDefaultPurchasesByItemsQuery = () => ({ export const getDefaultPurchasesByItemsQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
filterByOption: 'with-transactions', filterByOption: 'with-transactions',
itemsIds: [], itemsIds: [],
}); });

View File

@@ -25,8 +25,8 @@ export const getSalesByItemsQueryShema = () => {
* Retrieves the default query. * Retrieves the default query.
*/ */
export const getDefaultSalesByItemsQuery = () => ({ export const getDefaultSalesByItemsQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
filterByOption: 'with-transactions', filterByOption: 'with-transactions',
itemsIds: [], itemsIds: [],
}); });

View File

@@ -12,7 +12,7 @@ import { transformToForm } from '@/utils';
export function getDefaultTrialBalanceQuery() { export function getDefaultTrialBalanceQuery() {
return { return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'), fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'), toDate: moment().format('YYYY-MM-DD'),
basis: 'accrual', basis: 'accrual',
filterByOption: 'with-transactions', filterByOption: 'with-transactions',
branchesIds: [], branchesIds: [],

View File

@@ -9,7 +9,6 @@ import { tableRowTypesToClassnames } from '@/utils';
import { useVendorsBalanceColumns } from './components'; import { useVendorsBalanceColumns } from './components';
import { useVendorsBalanceSummaryContext } from './VendorsBalanceSummaryProvider'; import { useVendorsBalanceSummaryContext } from './VendorsBalanceSummaryProvider';
/** /**
* Vendors balance summary table. * Vendors balance summary table.
*/ */
@@ -18,7 +17,7 @@ export default function VendorsBalanceSummaryTable({
organizationName, organizationName,
}) { }) {
const { const {
VendorBalanceSummary: { table }, VendorBalanceSummary: { table, query },
} = useVendorsBalanceSummaryContext(); } = useVendorsBalanceSummaryContext();
// vendors balance summary columns. // vendors balance summary columns.
@@ -28,7 +27,7 @@ export default function VendorsBalanceSummaryTable({
<VendorBalanceFinancialSheet <VendorBalanceFinancialSheet
companyName={organizationName} companyName={organizationName}
sheetType={intl.get('vendors_balance_summary')} sheetType={intl.get('vendors_balance_summary')}
asDate={new Date()} asDate={query.as_date}
> >
<VendorBalanceDataTable <VendorBalanceDataTable
columns={columns} columns={columns}

View File

@@ -13,6 +13,7 @@ import VendorsTransactionsActionsBar from './VendorsTransactionsActionsBar';
import withVendorsTransactionsActions from './withVendorsTransactionsActions'; import withVendorsTransactionsActions from './withVendorsTransactionsActions';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { useVendorsTransactionsQuery } from './_utils';
/** /**
* Vendors transactions. * Vendors transactions.
@@ -22,11 +23,7 @@ function VendorsTransactions({
toggleVendorsTransactionsFilterDrawer, toggleVendorsTransactionsFilterDrawer,
}) { }) {
// filter // filter
const [filter, setFilter] = useState({ const [filter, setFilter] = useVendorsTransactionsQuery();
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
const handleFilterSubmit = (filter) => { const handleFilterSubmit = (filter) => {
const _filter = { const _filter = {

View File

@@ -1,6 +1,10 @@
// @ts-nocheck
import * as Yup from 'yup'; import * as Yup from 'yup';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import moment from 'moment'; import moment from 'moment';
import { useMemo } from 'react';
import { transformToForm } from '@/utils';
import { useAppQueryString } from '@/hooks';
/** /**
* The validation schema of vendors transactions. * The validation schema of vendors transactions.
@@ -19,7 +23,35 @@ export const getVendorTransactionsQuerySchema = () => {
* Retrieves the default query of vendors transactions. * Retrieves the default query of vendors transactions.
*/ */
export const getVendorsTransactionsDefaultQuery = () => ({ export const getVendorsTransactionsDefaultQuery = () => ({
fromDate: moment().toDate(), fromDate: moment().startOf('month').format('YYYY-MM-DD'),
toDate: moment().toDate(), toDate: moment().format('YYYY-MM-DD'),
vendorsIds: [], vendorsIds: [],
}); });
/**
* Parses the query of vendors transactions.
*/
const parseVendorsTransactionsQuery = (query) => {
const defaultQuery = getVendorsTransactionsDefaultQuery();
const transformed = {
...defaultQuery,
...transformToForm(query, defaultQuery),
};
return {
...transformed,
vendorsIds: transformed.vendorsIds ? transformed.vendorsIds : [],
};
};
/**
* Retrieves the query of vendors transactions.
*/
export const useVendorsTransactionsQuery = () => {
const [locationQuery, setLocationQuery] = useAppQueryString();
const query = useMemo(
() => parseVendorsTransactionsQuery(locationQuery),
[locationQuery],
);
return [query, setLocationQuery];
};

View File

@@ -138,26 +138,12 @@ export function useARAgingSummaryReport(query, props) {
method: 'get', method: 'get',
url: '/financial_statements/receivable_aging_summary', url: '/financial_statements/receivable_aging_summary',
params: query, params: query,
headers: {
Accept: 'application/json+table',
},
}, },
{ {
select: (res) => ({ select: (res) => res.data,
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: [],
},
...props, ...props,
}, },
); );
@@ -173,26 +159,12 @@ export function useAPAgingSummaryReport(query, props) {
method: 'get', method: 'get',
url: '/financial_statements/payable_aging_summary', url: '/financial_statements/payable_aging_summary',
params: query, params: query,
headers: {
Accept: 'application/json+table',
},
}, },
{ {
select: (res) => ({ select: (res) => res.data,
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: [],
},
...props, ...props,
}, },
); );