From 64c49654517f49649bdc6b010adb1423e24c5e79 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sun, 1 Aug 2021 11:36:03 +0200 Subject: [PATCH] feat: Add transactions by given reference report. --- .../api/controllers/FinancialStatements.ts | 5 ++ .../TransactionsByReference/index.ts | 87 +++++++++++++++++++ .../src/interfaces/TransactionsByReference.ts | 31 +++++++ server/src/interfaces/index.ts | 1 + .../TransactionsByReferenceReport.ts | 79 +++++++++++++++++ .../TransactionsByReferenceRepository.ts | 29 +++++++ .../TransactionsByReference/index.ts | 77 ++++++++++++++++ 7 files changed, 309 insertions(+) create mode 100644 server/src/api/controllers/FinancialStatements/TransactionsByReference/index.ts create mode 100644 server/src/interfaces/TransactionsByReference.ts create mode 100644 server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceReport.ts create mode 100644 server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceRepository.ts create mode 100644 server/src/services/FinancialStatements/TransactionsByReference/index.ts diff --git a/server/src/api/controllers/FinancialStatements.ts b/server/src/api/controllers/FinancialStatements.ts index dbda169f6..6c3aaf5dc 100644 --- a/server/src/api/controllers/FinancialStatements.ts +++ b/server/src/api/controllers/FinancialStatements.ts @@ -17,6 +17,7 @@ import TransactionsByCustomers from './FinancialStatements/TransactionsByCustome import TransactionsByVendors from './FinancialStatements/TransactionsByVendors'; import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow'; import InventoryDetailsController from './FinancialStatements/InventoryDetails'; +import TransactionsByReferenceController from './FinancialStatements/TransactionsByReference'; @Service() export default class FinancialStatementsService { @@ -87,6 +88,10 @@ export default class FinancialStatementsService { '/inventory-item-details', Container.get(InventoryDetailsController).router(), ); + router.use( + '/transactions-by-reference', + Container.get(TransactionsByReferenceController).router(), + ) return router; } } diff --git a/server/src/api/controllers/FinancialStatements/TransactionsByReference/index.ts b/server/src/api/controllers/FinancialStatements/TransactionsByReference/index.ts new file mode 100644 index 000000000..af6b83fe4 --- /dev/null +++ b/server/src/api/controllers/FinancialStatements/TransactionsByReference/index.ts @@ -0,0 +1,87 @@ +import { Inject, Service } from 'typedi'; +import { Router, Request, Response, NextFunction } from 'express'; +import { query, ValidationChain } from 'express-validator'; +import BaseController from 'api/controllers/BaseController'; +import TransactionsByReferenceService from 'services/FinancialStatements/TransactionsByReference'; + +@Service() +export default class TransactionsByReferenceController extends BaseController { + @Inject() + private transactionsByReferenceService: TransactionsByReferenceService; + + /** + * Router constructor. + */ + router() { + const router = Router(); + + router.get( + '/', + this.validationSchema, + this.validationResult, + this.asyncMiddleware(this.transactionsByReference.bind(this)) + ); + return router; + } + + /** + * Validation schema. + */ + get validationSchema(): ValidationChain[] { + return [ + query('reference_id').exists().isInt(), + query('reference_type').exists().isString(), + + query('number_format.precision') + .optional() + .isInt({ min: 0, max: 5 }) + .toInt(), + query('number_format.divide_on_1000').optional().isBoolean().toBoolean(), + query('number_format.negative_format') + .optional() + .isIn(['parentheses', 'mines']) + .trim() + .escape(), + ]; + } + + /** + * Retrieve transactions by the given reference type and id. + * @param {Request} req - Request object. + * @param {Response} res - Response. + * @param {NextFunction} next + * @returns + */ + public async transactionsByReference( + req: Request, + res: Response, + next: NextFunction + ) { + const { tenantId } = req; + const filter = this.matchedQueryData(req); + + try { + const transactions = + await this.transactionsByReferenceService.getTransactionsByReference( + tenantId, + filter + ); + const accept = this.accepts(req); + const acceptType = accept.types(['json']); + + switch (acceptType) { + case 'json': + default: + return res + .status(200) + .send(this.transformToJsonResponse(transactions)); + } + } catch (error) { + next(error); + } + } + + private transformToJsonResponse(transactions) { + return transactions; + } +} diff --git a/server/src/interfaces/TransactionsByReference.ts b/server/src/interfaces/TransactionsByReference.ts new file mode 100644 index 000000000..214fbb09e --- /dev/null +++ b/server/src/interfaces/TransactionsByReference.ts @@ -0,0 +1,31 @@ + + +export interface ITransactionsByReferenceQuery { + referenceType: string; + referenceId: string; +} + +export interface ITransactionsByReferenceAmount { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface ITransactionsByReferenceTransaction{ + credit: ITransactionsByReferenceAmount; + debit: ITransactionsByReferenceAmount; + + contactType: string; + formattedContactType: string; + + contactId: number; + + referenceType: string; + formattedReferenceType: string; + + referenceId: number; + + accountName: string; + accountCode: string; + accountId: number; +} \ No newline at end of file diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index 4e8dc9e78..3008dda21 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -55,6 +55,7 @@ export * from './CashFlow'; export * from './InventoryDetails'; export * from './LandedCost'; export * from './Entry'; +export * from './TransactionsByReference'; export interface I18nService { __: (input: string) => string; diff --git a/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceReport.ts b/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceReport.ts new file mode 100644 index 000000000..54f4fdfc6 --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceReport.ts @@ -0,0 +1,79 @@ +import { + IAccount, + IAccountTransaction, + INumberFormatQuery, + ITransactionsByReferenceQuery, + ITransactionsByReferenceTransaction, +} from 'interfaces'; +import FinancialSheet from '../FinancialSheet'; + +export default class TransactionsByReference extends FinancialSheet { + readonly transactions: IAccountTransaction[]; + readonly query: ITransactionsByReferenceQuery; + readonly baseCurrency: string; + readonly numberFormat: INumberFormatQuery; + + /** + * Constructor method. + * @param {IAccountTransaction[]} transactions + * @param {ITransactionsByReferenceQuery} query + * @param {string} baseCurrency + */ + constructor( + transactions: (IAccountTransaction & { account: IAccount }) [], + query: ITransactionsByReferenceQuery, + baseCurrency: string + ) { + super(); + + this.transactions = transactions; + this.query = query; + this.baseCurrency = baseCurrency; + this.numberFormat = this.query.numberFormat; + } + + /** + * Mappes the given account transaction to report transaction. + * @param {IAccountTransaction} transaction + * @returns {ITransactionsByReferenceTransaction} + */ + private transactionMapper = ( + transaction: IAccountTransaction + ): ITransactionsByReferenceTransaction => { + return { + credit: this.getAmountMeta(transaction.credit, { money: true }), + debit: this.getAmountMeta(transaction.debit, { money: true }), + + referenceTypeFormatted: transaction.referenceTypeFormatted, + referenceType: transaction.referenceType, + referenceId: transaction.referenceId, + + contactId: transaction.contactId, + contactType: transaction.contactType, + contactTypeFormatted: transaction.contactType, + + accountName: transaction.account.name, + accountCode: transaction.account.code, + accountId: transaction.accountId, + }; + }; + + /** + * Mappes the given accounts transactions to report transactions. + * @param {IAccountTransaction} transaction + * @returns {ITransactionsByReferenceTransaction} + */ + private transactionsMapper = ( + transactions: IAccountTransaction[] + ): ITransactionsByReferenceTransaction[] => { + return transactions.map(this.transactionMapper); + }; + + /** + * Retrieve the report data. + * @returns {ITransactionsByReferenceTransaction} + */ + public reportData(): ITransactionsByReferenceTransaction[] { + return this.transactionsMapper(this.transactions); + } +} diff --git a/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceRepository.ts b/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceRepository.ts new file mode 100644 index 000000000..c0602a88b --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByReference/TransactionsByReferenceRepository.ts @@ -0,0 +1,29 @@ +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { Service, Inject } from 'typedi'; +import { IAccount, IAccountTransaction, ITransactionsByReferenceQuery } from 'interfaces'; + +@Service() +export default class TransactionsByReferenceRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the accounts transactions of the givne reference id and type. + * @param {number} tenantId - + * @param {number} referenceId - Reference id. + * @param {string} referenceType - Reference type. + * @return {Promise} + */ + public getTransactions( + tenantId: number, + referenceId: number, + referenceType: string, + ): Promise<(IAccountTransaction & { account: IAccount }) []> { + const { AccountTransaction } = this.tenancy.models(tenantId); + + return AccountTransaction.query() + .where('reference_id', referenceId) + .where('reference_type', referenceType) + .withGraphFetched('account'); + } +} diff --git a/server/src/services/FinancialStatements/TransactionsByReference/index.ts b/server/src/services/FinancialStatements/TransactionsByReference/index.ts new file mode 100644 index 000000000..d42d0a2dd --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByReference/index.ts @@ -0,0 +1,77 @@ +import { Service, Inject } from 'typedi'; +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { + ITransactionsByReferenceQuery, + ITransactionsByReferenceTransaction, +} from 'interfaces'; +import TransactionsByReferenceRepository from './TransactionsByReferenceRepository'; +import TransactionsByReferenceReport from './TransactionsByReferenceReport'; + +@Service() +export default class TransactionsByReferenceService { + @Inject() + tenancy: HasTenancyService; + + @Inject('logger') + logger: any; + + @Inject() + reportRepository: TransactionsByReferenceRepository; + + /** + * Default query of transactions by reference report. + */ + get defaultQuery(): ITransactionsByReferenceQuery { + return { + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + }; + } + + /** + * Retrieve accounts transactions by given reference id and type. + * @param {number} tenantId + * @param {ITransactionsByReferenceQuery} filter + */ + public async getTransactionsByReference( + tenantId: number, + query: ITransactionsByReferenceQuery + ): Promise<{ + data: ITransactionsByReferenceTransaction[]; + }> { + const filter = { + ...this.defaultQuery, + ...query, + }; + + // Retrieve the accounts transactions of the given reference. + const transactions = await this.reportRepository.getTransactions( + tenantId, + filter.referenceId, + filter.referenceType + ); + + // Settings tenant service. + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + // Transactions by reference report. + const report = new TransactionsByReferenceReport( + transactions, + filter, + baseCurrency + ); + + return { + data: report.reportData(), + }; + } +}