WIP transactions by vendors report.

This commit is contained in:
a.bouhuolia
2021-05-06 04:53:33 +02:00
parent c57f2de970
commit 9b5ccf9248
13 changed files with 620 additions and 202 deletions

View File

@@ -14,6 +14,7 @@ import InventoryValuationController from './FinancialStatements/InventoryValuati
import CustomerBalanceSummaryController from './FinancialStatements/CustomerBalanceSummary';
import VendorBalanceSummaryController from './FinancialStatements/VendorBalanceSummary';
import TransactionsByCustomers from './FinancialStatements/TransactionsByCustomers';
import TransactionsByVendors from './FinancialStatements/TransactionsByVendors';
@Service()
export default class FinancialStatementsService {
@@ -72,6 +73,10 @@ export default class FinancialStatementsService {
'/transactions-by-customers',
Container.get(TransactionsByCustomers).router(),
);
router.use(
'/transactions-by-vendors',
Container.get(TransactionsByVendors).router(),
);
return router;
}
}

View File

@@ -0,0 +1,82 @@
import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator';
import { Inject } from 'typedi';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseFinancialReportController from '../BaseFinancialReportController';
import TransactionsByVendorsTableRows from 'services/FinancialStatements/TransactionsByVendor/TransactionsByVendorTableRows';
import TransactionsByVendorsService from 'services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService';
export default class TransactionsByVendorsReportController extends BaseFinancialReportController {
@Inject()
transactionsByVendorsService: TransactionsByVendorsService;
@Inject()
transactionsByVendorsTableRows: TransactionsByVendorsTableRows;
/**
* Router constructor.
*/
router() {
const router = Router();
router.get(
'/',
this.validationSchema,
asyncMiddleware(this.transactionsByVendors.bind(this))
);
return router;
}
/**
* Validation schema.
*/
get validationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
query('none_zero').optional().isBoolean().toBoolean(),
query('none_transactions').optional().isBoolean().toBoolean(),
];
}
/**
* Retrieve payable aging summary report.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
async transactionsByVendors(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = this.matchedQueryData(req);
try {
const {
data,
columns,
query,
} = await this.transactionsByVendorsService.transactionsByVendors(
tenantId,
filter
);
return res.status(200).send({
table: {
rows: this.transactionsByVendorsTableRows.tableRows(data),
},
});
return res.status(200).send({
data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query),
});
} catch (error) {
next(error);
}
}
}

View File

@@ -0,0 +1,32 @@
import { INumberFormatQuery } from './FinancialStatements';
export interface ITransactionsByContactsAmount {
amount: number;
formattedAmount: string;
currencyCode: string;
}
export interface ITransactionsByContactsTransaction {
date: string|Date,
credit: ITransactionsByContactsAmount;
debit: ITransactionsByContactsAmount;
runningBalance: ITransactionsByContactsAmount;
currencyCode: string;
referenceNumber: string;
transactionNumber: string;
createdAt: string|Date,
};
export interface ITransactionsByContactsContact {
openingBalance: ITransactionsByContactsAmount,
closingBalance: ITransactionsByContactsAmount,
transactions: ITransactionsByContactsTransaction[],
}
export interface ITransactionsByContactsFilter {
fromDate: Date;
toDate: Date;
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
noneZero: boolean;
}

View File

@@ -1,36 +1,24 @@
import { INumberFormatQuery } from './FinancialStatements';
import {
ITransactionsByContactsAmount,
ITransactionsByContactsTransaction,
ITransactionsByContactsFilter,
} from './TransactionsByContacts';
export interface ITransactionsByCustomersAmount {
amount: number;
formattedAmount: string;
currencyCode: string;
}
export interface ITransactionsByCustomersAmount
extends ITransactionsByContactsAmount {}
export interface ITransactionsByCustomersTransaction {
date: string|Date,
credit: ITransactionsByCustomersAmount;
debit: ITransactionsByCustomersAmount;
runningBalance: ITransactionsByCustomersAmount;
currencyCode: string;
referenceNumber: string;
transactionNumber: string;
createdAt: string|Date,
};
export interface ITransactionsByCustomersTransaction
extends ITransactionsByContactsTransaction {}
export interface ITransactionsByCustomersCustomer {
customerName: string;
openingBalance: any;
closingBalance: any;
openingBalance: ITransactionsByCustomersAmount;
closingBalance: ITransactionsByCustomersAmount;
transactions: ITransactionsByCustomersTransaction[];
}
export interface ITransactionsByCustomersFilter {
fromDate: Date;
toDate: Date;
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
noneZero: boolean;
}
export interface ITransactionsByCustomersFilter
extends ITransactionsByContactsFilter {}
export type ITransactionsByCustomersData = ITransactionsByCustomersCustomer[];

View File

@@ -0,0 +1,34 @@
import {
ITransactionsByContactsAmount,
ITransactionsByContactsTransaction,
ITransactionsByContactsFilter,
} from './TransactionsByContacts';
export interface ITransactionsByVendorsAmount
extends ITransactionsByContactsAmount {}
export interface ITransactionsByVendorsTransaction
extends ITransactionsByContactsTransaction {}
export interface ITransactionsByVendorsVendor {
vendorName: string;
openingBalance: ITransactionsByVendorsAmount;
closingBalance: ITransactionsByVendorsAmount;
transactions: ITransactionsByVendorsTransaction[];
}
export interface ITransactionsByVendorsFilter
extends ITransactionsByContactsFilter {}
export type ITransactionsByVendorsData = ITransactionsByVendorsVendor[];
export interface ITransactionsByVendorsStatement {
data: ITransactionsByVendorsData;
}
export interface ITransactionsByVendorsService {
transactionsByVendors(
tenantId: number,
filter: ITransactionsByVendorsFilter
): Promise<ITransactionsByVendorsStatement>;
}

View File

@@ -47,4 +47,6 @@ export * from './CustomerBalanceSummary';
export * from './VendorBalanceSummary';
export * from './ContactBalanceSummary';
export * from './TransactionsByCustomers';
export * from './TransactionsByContacts';
export * from './TransactionsByVendors';
export * from './Table';

View File

@@ -1,7 +1,103 @@
import { sumBy } from 'lodash';
import {
ITransactionsByContactsTransaction,
ITransactionsByContactsAmount,
ITransactionsByContacts,
IContact,
} from 'interfaces';
import FinancialSheet from '../FinancialSheet';
export default class TransactionsByContact extends FinancialSheet {
readonly contacts: IContact[];
/**
* Customer transaction mapper.
* @param {any} transaction -
* @return {Omit<ITransactionsByContactsTransaction, 'runningBalance'>}
*/
protected contactTransactionMapper(
transaction
): Omit<ITransactionsByContactsTransaction, 'runningBalance'> {
const currencyCode = 'USD';
export default class TransactionsByContact extends ITransactionsByContacts {
return {
credit: this.getContactAmount(transaction.credit, currencyCode),
debit: this.getContactAmount(transaction.debit, currencyCode),
currencyCode: 'USD',
transactionNumber: transaction.transactionNumber,
referenceNumber: transaction.referenceNumber,
date: transaction.date,
createdAt: transaction.createdAt,
};
}
}
/**
* Customer transactions mapper with running balance.
* @param {number} openingBalance
* @param {ITransactionsByContactsTransaction[]} transactions
* @returns {ITransactionsByContactsTransaction[]}
*/
protected contactTransactionRunningBalance(
openingBalance: number,
transactions: Omit<ITransactionsByContactsTransaction, 'runningBalance'>[]
): any {
let _openingBalance = openingBalance;
return transactions.map(
(transaction: ITransactionsByContactsTransaction) => {
_openingBalance += transaction.debit.amount;
_openingBalance -= transaction.credit.amount;
const runningBalance = this.getContactAmount(
_openingBalance,
transaction.currencyCode
);
return { ...transaction, runningBalance };
}
);
}
/**
* Retrieve the customer closing balance from the given transactions and opening balance.
* @param {number} customerTransactions
* @param {number} openingBalance
* @returns {number}
*/
protected getContactClosingBalance(
customerTransactions: ITransactionsByContactsTransaction[],
openingBalance: number
): number {
const closingBalance = openingBalance;
const totalCredit = sumBy(customerTransactions, 'credit');
const totalDebit = sumBy(customerTransactions, 'debit');
return closingBalance + (totalDebit - totalCredit);
}
/**
* Retrieve the given customer opening balance from the given customer id.
* @param {number} customerId
* @returns {number}
*/
protected getContactOpeningBalance(customerId: number): number {
return 0;
}
/**
* Retrieve the customer amount format meta.
* @param {number} amount
* @param {string} currencyCode
* @returns {ITransactionsByContactsAmount}
*/
protected getContactAmount(
amount: number,
currencyCode: string
): ITransactionsByContactsAmount {
return {
amount,
formattedAmount: this.formatNumber(amount, { currencyCode }),
currencyCode,
};
}
}

View File

@@ -0,0 +1,75 @@
import { tableMapper, tableRowMapper } from 'utils';
import {
ITransactionsByContactsContact,
ITableRow,
} from 'interfaces';
enum ROW_TYPE {
OPENING_BALANCE = 'OPENING_BALANCE',
CLOSING_BALANCE = 'CLOSING_BALANCE',
TRANSACTION = 'TRANSACTION',
CUSTOMER = 'CUSTOMER',
}
export default class TransactionsByContactsTableRows {
/**
* Retrieve the table rows of contact transactions.
* @param {ITransactionsByCustomersCustomer} contact
* @returns {ITableRow[]}
*/
protected contactTransactions(
contact: ITransactionsByContactsContact
): ITableRow[] {
const columns = [
{ key: 'date', accessor: 'date' },
{ key: 'account', accessor: 'account.name' },
{ key: 'referenceType', accessor: 'referenceType' },
{ key: 'transactionType', accessor: 'transactionType' },
{ key: 'credit', accessor: 'credit.formattedAmount' },
{ key: 'debit', accessor: 'debit.formattedAmount' },
];
return tableMapper(contact.transactions, columns, {
rowTypes: [ROW_TYPE.TRANSACTION],
});
}
/**
* Retrieve the table row of contact opening balance.
* @param {ITransactionsByCustomersCustomer} contact
* @returns {ITableRow}
*/
protected contactOpeningBalance(
contact: ITransactionsByContactsContact
): ITableRow {
const columns = [
{ key: 'openingBalanceLabel', value: 'Opening balance' },
{
key: 'openingBalanceValue',
accessor: 'openingBalance.formattedAmount',
},
];
return tableRowMapper(contact, columns, {
rowTypes: [ROW_TYPE.OPENING_BALANCE],
});
}
/**
* Retrieve the table row of contact closing balance.
* @param {ITransactionsByCustomersCustomer} contact -
* @returns {ITableRow}
*/
protected contactClosingBalance(
contact: ITransactionsByContactsContact
): ITableRow {
const columns = [
{ key: 'openingBalanceLabel', value: 'Closing balance' },
{
key: 'openingBalanceValue',
accessor: 'closingBalance.formattedAmount',
},
];
return tableRowMapper(contact, columns, {
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
});
}
}

View File

@@ -1,6 +1,5 @@
import * as R from 'ramda';
import { sumBy } from 'lodash';
import FinancialSheet from '../FinancialSheet';
import {
ITransactionsByCustomersTransaction,
ITransactionsByCustomersFilter,
@@ -11,8 +10,9 @@ import {
IAccountTransaction,
ICustomer,
} from 'interfaces';
import TransactionsByContact from '../TransactionsByContact/TransactionsByContact';
export default class TransactionsByCustomers extends FinancialSheet {
export default class TransactionsByCustomers extends TransactionsByContact {
readonly customers: ICustomer[];
readonly transactionsByContact: any;
readonly filter: ITransactionsByCustomersFilter;
@@ -40,53 +40,6 @@ export default class TransactionsByCustomers extends FinancialSheet {
this.numberFormat = this.filter.numberFormat;
}
/**
* Customer transaction mapper.
* @param {any} transaction -
* @return {Omit<ITransactionsByCustomersTransaction, 'runningBalance'>}
*/
private customerTransactionMapper(
transaction
): Omit<ITransactionsByCustomersTransaction, 'runningBalance'> {
const currencyCode = 'USD';
return {
credit: this.getCustomerAmount(transaction.credit, currencyCode),
debit: this.getCustomerAmount(transaction.debit, currencyCode),
currencyCode: 'USD',
transactionNumber: transaction.transactionNumber,
referenceNumber: transaction.referenceNumber,
date: transaction.date,
createdAt: transaction.createdAt,
};
}
/**
* Customer transactions mapper with running balance.
* @param {number} openingBalance
* @param {ITransactionsByCustomersTransaction[]} transactions
* @returns {ITransactionsByCustomersTransaction[]}
*/
private customerTransactionRunningBalance(
openingBalance: number,
transactions: Omit<ITransactionsByCustomersTransaction, 'runningBalance'>[]
): any {
let _openingBalance = openingBalance;
return transactions.map(
(transaction: ITransactionsByCustomersTransaction) => {
_openingBalance += transaction.debit.amount;
_openingBalance -= transaction.credit.amount;
const runningBalance = this.getCustomerAmount(
_openingBalance,
transaction.currencyCode
);
return { ...transaction, runningBalance };
}
);
}
/**
* Retrieve the customer transactions from the given customer id and opening balance.
* @param {number} customerId - Customer id.
@@ -100,38 +53,11 @@ export default class TransactionsByCustomers extends FinancialSheet {
const transactions = this.transactionsByContact.get(customerId + '') || [];
return R.compose(
R.curry(this.customerTransactionRunningBalance)(openingBalance),
R.map(this.customerTransactionMapper.bind(this))
R.curry(this.contactTransactionRunningBalance)(openingBalance),
R.map(this.contactTransactionMapper.bind(this))
).bind(this)(transactions);
}
/**
* Retrieve the customer closing balance from the given transactions and opening balance.
* @param {number} customerTransactions
* @param {number} openingBalance
* @returns {number}
*/
private getCustomerClosingBalance(
customerTransactions: ITransactionsByCustomersTransaction[],
openingBalance: number
): number {
const closingBalance = openingBalance;
const totalCredit = sumBy(customerTransactions, 'credit');
const totalDebit = sumBy(customerTransactions, 'debit');
return closingBalance + (totalDebit - totalCredit);
}
/**
* Retrieve the given customer opening balance from the given customer id.
* @param {number} customerId
* @returns {number}
*/
private getCustomerOpeningBalance(customerId: number): number {
return 0;
}
/**
* Customer section mapper.
* @param {ICustomer} customer
@@ -140,17 +66,17 @@ export default class TransactionsByCustomers extends FinancialSheet {
private customerMapper(
customer: ICustomer
): ITransactionsByCustomersCustomer {
const openingBalance = this.getCustomerOpeningBalance(1);
const openingBalance = this.getContactOpeningBalance(1);
const transactions = this.customerTransactions(customer.id, openingBalance);
const closingBalance = this.getCustomerClosingBalance(transactions, 0);
const closingBalance = this.getContactClosingBalance(transactions, 0);
return {
customerName: customer.displayName,
openingBalance: this.getCustomerAmount(
openingBalance: this.getContactAmount(
openingBalance,
customer.currencyCode
),
closingBalance: this.getCustomerAmount(
closingBalance: this.getContactAmount(
closingBalance,
customer.currencyCode
),
@@ -158,23 +84,6 @@ export default class TransactionsByCustomers extends FinancialSheet {
};
}
/**
* Retrieve the customer amount format meta.
* @param {number} amount
* @param {string} currencyCode
* @returns {ITransactionsByCustomersAmount}
*/
private getCustomerAmount(
amount: number,
currencyCode: string
): ITransactionsByCustomersAmount {
return {
amount,
formattedAmount: this.formatNumber(amount, { currencyCode }),
currencyCode,
};
}
/**
* Retrieve the customers sections of the report.
* @param {ICustomer[]} customers

View File

@@ -1,6 +1,7 @@
import * as R from 'ramda';
import { tableRowMapper, tableMapper } from 'utils';
import { ITransactionsByCustomersCustomer, ITableRow } from 'interfaces';
import TransactionsByContactsTableRows from '../TransactionsByContact/TransactionsByContactTableRows';
enum ROW_TYPE {
OPENING_BALANCE = 'OPENING_BALANCE',
@@ -9,102 +10,37 @@ enum ROW_TYPE {
CUSTOMER = 'CUSTOMER',
}
export default class TransactionsByCustomersTableRows {
/**
* Retrieve the table rows of customer transactions.
* @param {ITransactionsByCustomersCustomer} customer
* @returns {ITableRow[]}
*/
private customerTransactions(
customer: ITransactionsByCustomersCustomer
): ITableRow[] {
const columns = [
{ key: 'date', accessor: 'date' },
{ key: 'account', accessor: 'account.name' },
{ key: 'referenceType', accessor: 'referenceType' },
{ key: 'transactionType', accessor: 'transactionType' },
{ key: 'credit', accessor: 'credit.formattedAmount' },
{ key: 'debit', accessor: 'debit.formattedAmount' },
];
return tableMapper(customer.transactions, columns, {
rowTypes: [ROW_TYPE.TRANSACTION]
});
}
/**
* Retrieve the table row of customer opening balance.
* @param {ITransactionsByCustomersCustomer} customer
* @returns {ITableRow}
*/
private customerOpeningBalance(
customer: ITransactionsByCustomersCustomer
): ITableRow {
const columns = [
{ key: 'openingBalanceLabel', value: 'Opening balance' },
{
key: 'openingBalanceValue',
accessor: 'openingBalance.formattedAmount',
},
];
return tableRowMapper(customer, columns, {
rowTypes: [ROW_TYPE.OPENING_BALANCE]
});
}
/**
* Retrieve the table row of customer closing balance.
* @param {ITransactionsByCustomersCustomer} customer -
* @returns {ITableRow}
*/
private customerClosingBalance(
customer: ITransactionsByCustomersCustomer
): ITableRow {
const columns = [
{ key: 'openingBalanceLabel', value: 'Closing balance' },
{
key: 'openingBalanceValue',
accessor: 'closingBalance.formattedAmount',
},
];
return tableRowMapper(customer, columns, {
rowTypes: [ROW_TYPE.CLOSING_BALANCE]
});
}
export default class TransactionsByCustomersTableRows extends TransactionsByContactsTableRows {
/**
* Retrieve the table row of customer details.
* @param {ITransactionsByCustomersCustomer} customer -
* @param {ITransactionsByCustomersCustomer} customer -
* @returns {ITableRow[]}
*/
private customerDetails(
customer: ITransactionsByCustomersCustomer
) {
private customerDetails(customer: ITransactionsByCustomersCustomer) {
const columns = [{ key: 'customerName', accessor: 'customerName' }];
return {
...tableRowMapper(customer, columns, { rowTypes: [ROW_TYPE.CUSTOMER] }),
children: R.pipe(
R.append(this.customerOpeningBalance(customer)),
R.concat(this.customerTransactions(customer)),
R.append(this.customerClosingBalance(customer)),
R.append(this.contactOpeningBalance(customer)),
R.concat(this.contactTransactions(customer)),
R.append(this.contactClosingBalance(customer))
)([]),
}
};
}
/**
* Retrieve the table rows of the customer section.
* @param {ITransactionsByCustomersCustomer} customer
* @param {ITransactionsByCustomersCustomer} customer
* @returns {ITableRow[]}
*/
private customerRowsMapper(customer: ITransactionsByCustomersCustomer) {
return R.pipe(
R.append(this.customerDetails(customer)),
).bind(this)([]);
return R.pipe(R.append(this.customerDetails(customer))).bind(this)([]);
}
/**
* Retrieve the table rows of transactions by customers report.
* @param {ITransactionsByCustomersCustomer[]} customers
* @param {ITransactionsByCustomersCustomer[]} customers
* @returns {ITableRow[]}
*/
public tableRows(customers: ITransactionsByCustomersCustomer[]): ITableRow[] {

View File

@@ -0,0 +1,114 @@
import * as R from 'ramda';
import { sumBy } from 'lodash';
import {
ITransactionsByVendorsFilter,
ITransactionsByVendorsTransaction,
ITransactionsByVendorsVendor,
ITransactionsByVendorsAmount,
ITransactionsByVendorsData,
IAccountTransaction,
INumberFormatQuery,
IVendor
} from 'interfaces';
import TransactionsByContact from "../TransactionsByContact/TransactionsByContact";
export default class TransactionsByVendors extends TransactionsByContact{
readonly contacts: IVendor[];
readonly transactionsByContact: any;
readonly filter: ITransactionsByVendorsFilter;
readonly baseCurrency: string;
readonly numberFormat: INumberFormatQuery;
/**
* Constructor method.
* @param {IVendor} vendors
* @param {Map<number, IAccountTransaction[]>} transactionsByContact
* @param {string} baseCurrency
*/
constructor(
vendors: IVendor[],
transactionsByContact: Map<number, IAccountTransaction[]>,
filter: ITransactionsByVendorsFilter,
baseCurrency: string
) {
super();
this.contacts = vendors;
this.transactionsByContact = transactionsByContact;
this.baseCurrency = baseCurrency;
this.filter = filter;
this.numberFormat = this.filter.numberFormat;
}
/**
* Retrieve the vendor transactions from the given vendor id and opening balance.
* @param {number} vendorId - Vendor id.
* @param {number} openingBalance - Opening balance amount.
* @returns {ITransactionsByVendorsTransaction[]}
*/
private vendorTransactions(
vendorId: number,
openingBalance: number
): ITransactionsByVendorsTransaction[] {
const transactions = this.transactionsByContact.get(vendorId + '') || [];
return R.compose(
R.curry(this.contactTransactionRunningBalance)(openingBalance),
R.map(this.contactTransactionMapper.bind(this))
).bind(this)(transactions);
}
/**
* Vendor section mapper.
* @param {IVendor} vendor
* @returns {ITransactionsByVendorsVendor}
*/
private vendorMapper(
vendor: IVendor
): ITransactionsByVendorsVendor {
const openingBalance = this.getContactOpeningBalance(1);
const transactions = this.vendorTransactions(vendor.id, openingBalance);
const closingBalance = this.getContactClosingBalance(transactions, 0);
return {
vendorName: vendor.displayName,
openingBalance: this.getContactAmount(
openingBalance,
vendor.currencyCode
),
closingBalance: this.getContactAmount(
closingBalance,
vendor.currencyCode
),
transactions,
};
}
/**
* Retrieve the vendors sections of the report.
* @param {IVendor[]} vendors
* @returns {ITransactionsByVendorsVendor[]}
*/
private vendorsMapper(
vendors: IVendor[]
): ITransactionsByVendorsVendor[] {
return R.compose(R.map(this.vendorMapper.bind(this))).bind(this)(
vendors
);
}
/**
* Retrieve the report data.
* @returns {ITransactionsByVendorsData}
*/
public reportData(): ITransactionsByVendorsData {
return this.vendorsMapper(this.contacts);
}
/**
* Retrieve the report columns.
*/
public reportColumns() {
return [];
}
}

View File

@@ -0,0 +1,96 @@
import { Inject } from 'typedi';
import moment from 'moment';
import { groupBy } from 'lodash';
import TenancyService from 'services/Tenancy/TenancyService';
import {
ITransactionsByVendorsService,
ITransactionsByVendorsFilter,
ITransactionsByVendorsStatement,
} from 'interfaces';
import TransactionsByVendor from './TransactionsByVendor';
export default class TransactionsByVendorsService
implements ITransactionsByVendorsService {
@Inject()
tenancy: TenancyService;
@Inject('logger')
logger: any;
/**
* Defaults balance sheet filter query.
* @return {IVendorBalanceSummaryQuery}
*/
get defaultQuery(): ITransactionsByVendorsFilter {
return {
fromDate: moment().format('YYYY-MM-DD'),
toDate: moment().format('YYYY-MM-DD'),
numberFormat: {
precision: 2,
divideOn1000: false,
showZero: false,
formatMoney: 'total',
negativeFormat: 'mines',
},
comparison: {
percentageOfColumn: true,
},
noneZero: false,
noneTransactions: false,
};
}
/**
* Retrieve transactions by by the customers.
* @param {number} tenantId
* @param {ITransactionsByVendorsFilter} query
* @return {Promise<ITransactionsByVendorsStatement>}
*/
public async transactionsByVendors(
tenantId: number,
query: ITransactionsByVendorsFilter
): Promise<ITransactionsByVendorsStatement> {
const { transactionsRepository } = this.tenancy.repositories(tenantId);
const { Vendor } = this.tenancy.models(tenantId);
// Settings tenant service.
const settings = this.tenancy.settings(tenantId);
const baseCurrency = settings.get({
group: 'organization',
key: 'base_currency',
});
const filter = {
...this.defaultQuery,
...query,
};
const vendors = await Vendor.query().orderBy('displayName');
// Retrieve all journal transactions based on the given query.
const transactions = await transactionsRepository.journal({
fromDate: query.fromDate,
toDate: query.toDate,
});
// Transactions map by contact id.
const transactionsMap = new Map(
Object.entries(groupBy(transactions, 'contactId'))
);
// Transactions by customers data mapper.
const reportInstance = new TransactionsByVendor(
vendors,
transactionsMap,
filter,
baseCurrency
);
// Retrieve the report data.
const reportData = reportInstance.reportData();
// Retireve the report columns.
const reportColumns = reportInstance.reportColumns();
return {
data: reportData,
columns: reportColumns,
};
}
}

View File

@@ -0,0 +1,49 @@
import * as R from 'ramda';
import { tableRowMapper } from 'utils';
import { ITransactionsByVendorsVendor, ITableRow } from 'interfaces';
import TransactionsByContactsTableRows from '../TransactionsByContact/TransactionsByContactTableRows';
enum ROW_TYPE {
OPENING_BALANCE = 'OPENING_BALANCE',
CLOSING_BALANCE = 'CLOSING_BALANCE',
TRANSACTION = 'TRANSACTION',
VENDOR = 'VENDOR',
}
export default class TransactionsByVendorsTableRows extends TransactionsByContactsTableRows {
/**
* Retrieve the table row of vendor details.
* @param {ITransactionsByVendorsVendor} vendor -
* @returns {ITableRow[]}
*/
private vendorDetails(vendor: ITransactionsByVendorsVendor) {
const columns = [{ key: 'vendorName', accessor: 'vendorName' }];
return {
...tableRowMapper(vendor, columns, { rowTypes: [ROW_TYPE.VENDOR] }),
children: R.pipe(
R.append(this.contactOpeningBalance(vendor)),
R.concat(this.contactTransactions(vendor)),
R.append(this.contactClosingBalance(vendor))
)([]),
};
}
/**
* Retrieve the table rows of the vendor section.
* @param {ITransactionsByVendorsVendor} vendor
* @returns {ITableRow[]}
*/
private vendorRowsMapper(vendor: ITransactionsByVendorsVendor) {
return R.pipe(R.append(this.vendorDetails(vendor))).bind(this)([]);
}
/**
* Retrieve the table rows of transactions by vendors report.
* @param {ITransactionsByVendorsVendor[]} vendors
* @returns {ITableRow[]}
*/
public tableRows(vendors: ITransactionsByVendorsVendor[]): ITableRow[] {
return R.map(this.vendorRowsMapper.bind(this))(vendors);
}
}