From 60b1bc9ed71ecbb32a28f56a9bf767b1c5d843a7 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 4 Jan 2024 17:22:13 +0200 Subject: [PATCH] feat(server): wip journal and general ledger table layer --- .../src/interfaces/GeneralLedgerSheet.ts | 1 + .../server/src/interfaces/JournalReport.ts | 8 +- .../GeneralLedger/GeneralLedger.ts | 2 + .../GeneralLedger/GeneralLedgerTable.ts | 66 +++++++-- .../JournalSheet/JournalSheet.ts | 6 +- .../JournalSheet/JournalSheetService.ts | 22 ++- .../JournalSheet/JournalSheetTable.ts | 138 +++++++++++++++--- .../FinancialStatements/JournalSheet/types.ts | 6 + 8 files changed, 212 insertions(+), 37 deletions(-) create mode 100644 packages/server/src/services/FinancialStatements/JournalSheet/types.ts diff --git a/packages/server/src/interfaces/GeneralLedgerSheet.ts b/packages/server/src/interfaces/GeneralLedgerSheet.ts index 5141ddb47..9951bf3b2 100644 --- a/packages/server/src/interfaces/GeneralLedgerSheet.ts +++ b/packages/server/src/interfaces/GeneralLedgerSheet.ts @@ -37,6 +37,7 @@ export interface IGeneralLedgerSheetAccountTransaction { referenceType?: string, date: Date|string, + dateFormatted: string; }; export interface IGeneralLedgerSheetAccountBalance { diff --git a/packages/server/src/interfaces/JournalReport.ts b/packages/server/src/interfaces/JournalReport.ts index 2a8625f9d..823e8b887 100644 --- a/packages/server/src/interfaces/JournalReport.ts +++ b/packages/server/src/interfaces/JournalReport.ts @@ -18,6 +18,7 @@ export interface IJournalReportQuery { export interface IJournalReportEntriesGroup { id: string; + dateFormatted: string; entries: IJournalEntry[]; currencyCode: string; credit: number; @@ -41,5 +42,10 @@ export interface IJournalTable extends IFinancialTable { meta: IJournalSheetMeta; } +export type IJournalTableData = IJournalReportEntriesGroup[]; -export type IJournalTableData = IJournalReportEntriesGroup[]; \ No newline at end of file +export interface IJournalSheet { + data: IJournalTableData; + query: IJournalReportQuery; + meta: IJournalSheetMeta; +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts index 512ed37d7..507b9deb4 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts @@ -10,6 +10,7 @@ import { IContact, } from '@/interfaces'; import FinancialSheet from '../FinancialSheet'; +import moment from 'moment'; /** * General ledger sheet. @@ -88,6 +89,7 @@ export default class GeneralLedgerSheet extends FinancialSheet { const newEntry = { date: entry.date, + dateFromatted: moment(entry.date).format('YYYY/MM/DD'), entryId: entry.id, referenceType: entry.referenceType, diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts index 938052ceb..2529105f7 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts @@ -1,6 +1,7 @@ import * as R from 'ramda'; import { IGeneralLedgerSheetAccount, + IGeneralLedgerSheetAccountTransaction, IGeneralLedgerSheetData, IGeneralLedgerSheetQuery, ITableColumn, @@ -35,15 +36,34 @@ export class GeneralLedgerTable extends R.compose( * Retrieves the common table accessors. * @returns {ITableColumnAccessor[]} */ - private commonColumnsAccessors(): ITableColumnAccessor[] { + private accountColumnsAccessors(): ITableColumnAccessor[] { + return [ + { key: 'date', accessor: 'name' }, + { key: 'account_name', accessor: '_empty_' }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: 'description' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'amount.formattedAmount' }, + { key: 'running_balance', accessor: 'openingBalance.formattedAmount' }, + ]; + } + + /** + * Retrieves the transaction column accessors. + * @returns {ITableColumnAccessor[]} + */ + private transactionColumnAccessors(): ITableColumnAccessor[] { return [ { key: 'date', accessor: 'date' }, + { key: 'account_name', accessor: 'name' }, { key: 'reference_type', accessor: 'referenceTypeFormatted' }, - { key: 'reference_number', accessor: 'reference_number' }, + { key: 'reference_number', accessor: 'referenceNumber' }, { key: 'currency_code', accessor: 'currencyCode' }, { key: 'credit', accessor: 'formattedCredit' }, { key: 'debit', accessor: 'formattedDebit' }, - { key: 'running_balance', accessor: 'formattedRunningBalance' }, + { key: 'running_balance', accessor: 'runningBalance.formattedAmount' }, ]; } @@ -54,24 +74,52 @@ export class GeneralLedgerTable extends R.compose( private commonColumns(): ITableColumn[] { return [ { key: 'date', label: 'Date' }, - { key: 'reference_type', label: 'Reference Type' }, - { key: 'reference_number', label: 'Reference Number' }, - { key: 'currency_code', label: 'Currency Code' }, + { key: 'account_name', label: 'Account Name' }, + { key: 'reference_type', label: 'Transaction Type' }, + { key: 'reference_number', label: 'Transaction Number' }, + { key: 'description', label: 'Description' }, { key: 'credit', label: 'Credit' }, { key: 'debit', label: 'Debit' }, + { key: 'amount', label: 'Amount' }, { key: 'running_balance', label: 'Running Balance' }, ]; } + /** + * Maps the given transaction node to table row. + * @param {IGeneralLedgerSheetAccountTransaction} transaction + * @returns {ITableRow} + */ + private transactionMapper = ( + transaction: IGeneralLedgerSheetAccountTransaction + ): ITableRow => { + const columns = this.transactionColumnAccessors(); + + return tableRowMapper(transaction, columns, {}); + }; + + /** + * Maps the given transactions nodes to table rows. + * @param {IGeneralLedgerSheetAccountTransaction[]} transactions + * @returns {ITableRow[]} + */ + private transactionsMapper = ( + transactions: IGeneralLedgerSheetAccountTransaction[] + ): ITableRow[] => { + return R.map(this.transactionMapper)(transactions); + }; + /** * Maps the given account node to the table rows. * @param {IGeneralLedgerSheetAccount} account * @returns {ITableRow} */ private accountMapper = (account: IGeneralLedgerSheetAccount): ITableRow => { - const columns = this.commonColumnsAccessors(); + const columns = this.accountColumnsAccessors(); + const row = tableRowMapper(account, columns, {}); + const transactions = this.transactionsMapper(account.transactions); - return tableRowMapper(account, columns, {}); + return R.assoc('children', transactions)(row); }; /** @@ -82,7 +130,7 @@ export class GeneralLedgerTable extends R.compose( private accountsMapper = ( accounts: IGeneralLedgerSheetAccount[] ): ITableRow[] => { - return R.compose(R.map(this.accountMapper))(accounts); + return this.mapNodesDeep(accounts, this.accountMapper)l }; /** diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts index ee184a5a1..ec3d5f8ef 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts @@ -6,8 +6,10 @@ import { IJournalReportQuery, IJournalReport, IContact, + IJournalTableData, } from '@/interfaces'; import FinancialSheet from '../FinancialSheet'; +import moment from 'moment'; export default class JournalSheet extends FinancialSheet { readonly tenantId: number; @@ -96,6 +98,8 @@ export default class JournalSheet extends FinancialSheet { return { date: groupEntry.date, + dateFormatted: moment(groupEntry.date).format('YYYY/MM/DD'), + referenceType: groupEntry.referenceType, referenceId: groupEntry.referenceId, referenceTypeFormatted: this.i18n.__(groupEntry.referenceTypeFormatted), @@ -131,7 +135,7 @@ export default class JournalSheet extends FinancialSheet { * Retrieve journal report. * @return {IJournalReport} */ - reportData(): IJournalReport { + reportData(): IJournalTableData { return this.entriesWalker(this.journal.entries); } } diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts index afb0b10f3..69a319ff3 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts @@ -1,6 +1,11 @@ import { Service, Inject } from 'typedi'; import moment from 'moment'; -import { IJournalReportQuery, IJournalSheetMeta } from '@/interfaces'; +import { + IJournalReportQuery, + IJournalSheet, + IJournalSheetMeta, + IJournalTableData, +} from '@/interfaces'; import JournalSheet from './JournalSheet'; import TenancyService from '@/services/Tenancy/TenancyService'; import Journal from '@/services/Accounting/JournalPoster'; @@ -11,13 +16,10 @@ import { parseBoolean, transformToMap } from 'utils'; @Service() export class JournalSheetService { @Inject() - tenancy: TenancyService; + private tenancy: TenancyService; @Inject() - inventoryService: InventoryService; - - @Inject('logger') - logger: any; + private inventoryService: InventoryService; /** * Default journal sheet filter queyr. @@ -66,9 +68,13 @@ export class JournalSheetService { /** * Journal sheet. * @param {number} tenantId - * @param {IJournalSheetFilterQuery} query + * @param {IJournalReportQuery} query + * @returns {Promise} */ - async journalSheet(tenantId: number, query: IJournalReportQuery) { + async journalSheet( + tenantId: number, + query: IJournalReportQuery + ): Promise { const i18n = this.tenancy.i18n(tenantId); const { accountRepository, transactionsRepository, contactRepository } = this.tenancy.repositories(tenantId); diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts index 2a376a854..2854154e2 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; import { + IJournalEntry, IJournalReport, IJournalReportEntriesGroup, IJournalReportQuery, @@ -12,6 +13,7 @@ import { tableRowMapper } from '@/utils'; import { FinancialTable } from '../FinancialTable'; import { FinancialSheetStructure } from '../FinancialSheetStructure'; import FinancialSheet from '../FinancialSheet'; +import { first } from 'lodash'; export class JournalSheetTable extends R.compose( FinancialTable, @@ -21,6 +23,12 @@ export class JournalSheetTable extends R.compose( private query: IJournalReportQuery; private i18n: any; + /** + * Constructor method. + * @param {IJournalTableData} data + * @param {IJournalReportQuery} query + * @param i18n + */ constructor(data: IJournalTableData, query: IJournalReportQuery, i18n: any) { super(); this.data = data; @@ -32,47 +40,141 @@ export class JournalSheetTable extends R.compose( * Retrieves the common table accessors. * @returns {ITableColumnAccessor[]} */ - private commonColumnsAccessors = (): ITableColumnAccessor[] => { + private groupColumnsAccessors = (): ITableColumnAccessor[] => { return [ { key: 'date', accessor: 'date' }, - { key: 'reference_type', accessor: 'referenceTypeFormatted' }, - { key: 'reference_number', accessor: 'reference_number' }, - { key: 'currency_code', accessor: 'currencyCode' }, + { key: 'transaction_type', accessor: 'referenceTypeFormatted' }, + { key: 'transaction_number', accessor: 'referenceNumber' }, + { key: 'description', accessor: 'entry.description' }, + { key: 'account_code', accessor: 'entry.accountCode' }, + { key: 'account_name', accessor: 'entry.accountName' }, + { key: 'credit', accessor: 'entry.formattedCredit' }, + { key: 'debit', accessor: 'entry.formattedDebit' }, + ]; + }; + + /** + * Retrieves the group entry accessors. + * @returns {ITableColumnAccessor[]} + */ + private entryColumnsAccessors = (): ITableColumnAccessor[] => { + return [ + { key: 'date', accessor: '_empty_' }, + { key: 'transaction_type', accessor: '_empty_' }, + { key: 'transaction_number', accessor: '_empty_' }, + { key: 'description', accessor: 'description' }, + { key: 'account_code', accessor: 'accountCode' }, + { key: 'account_name', accessor: 'accountName' }, { key: 'credit', accessor: 'formattedCredit' }, { key: 'debit', accessor: 'formattedDebit' }, ]; }; + private totalEntryColumnAccessors = (): ITableColumnAccessor[] => { + return [ + { key: 'date', accessor: '_empty_' }, + { key: 'transaction_type', accessor: '_empty_' }, + { key: 'transaction_number', accessor: '_empty_' }, + { key: 'description', accessor: '_empty_' }, + { key: 'account_code', accessor: '_empty_' }, + { key: 'account_name', accessor: '_empty_' }, + { key: 'credit', accessor: 'formattedCredit' }, + { key: 'debit', accessor: 'formattedDebit' }, + ]; + }; + + /** + * Retrieves the common columns. + * @returns {ITableColumn[]} + */ private commonColumns(): ITableColumn[] { return [ { key: 'date', label: 'Date' }, - { key: 'reference_type', label: 'Reference Type' }, - { key: 'reference_type', label: 'Reference Number' }, - { key: 'currency_code', label: 'Currency Code' }, + { key: 'transaction_type', label: 'Transaction Type' }, + { key: 'transaction_number', label: 'Num.' }, + { key: 'description', label: 'Description' }, + { key: 'account_code', label: 'Acc. Code' }, + { key: 'account_name', label: 'Account' }, { key: 'credit', label: 'Credit' }, { key: 'debit', label: 'Debit' }, ]; } /** - * + * Maps the group and first entry to table row. + * @param {IJournalReportEntriesGroup} group + * @returns {ITableRow} */ - private entryGroupMapper = (group: IJournalReportEntriesGroup) => { - const columns = this.commonColumnsAccessors(); - - return tableRowMapper(group, columns, {}); + private firstEntryGroupMapper = ( + group: IJournalReportEntriesGroup + ): ITableRow => { + const meta = { + rowTypes: [ROW_TYPE.ENTRY], + }; + const computedGroup = { ...group, entry: first(group.entries) }; + const columns = this.groupColumnsAccessors(); + return tableRowMapper(computedGroup, columns, meta); }; /** - * + * Maps the given group entry to table rows. + * @param {IJournalEntry} entry + * @returns {ITableRow} */ - private entryMapper = () => {}; + private entryMapper = (entry: IJournalEntry): ITableRow => { + const columns = this.entryColumnsAccessors(); + const meta = { + rowTypes: [ROW_TYPE.ENTRY], + }; + return tableRowMapper(entry, columns, meta); + }; /** - * + * Maps the given group entries to table rows. + * @param {IJournalReportEntriesGroup} group + * @returns {ITableRow[]} */ - private entriesGroupsMapper = (entries: IJournalReportEntriesGroup[]) => { - return R.compose(R.map(this.entryGroupMapper))(entries); + private entriesMapper = (group: IJournalReportEntriesGroup): ITableRow[] => { + const entries = R.remove(0, 1, group.entries); + + return R.map(this.entryMapper, entries); + }; + + /** + * Maps the given group entry to total table row. + * @param {IJournalReportEntriesGroup} group + * @returns {ITableRow} + */ + public totalEntryMapper = (group: IJournalReportEntriesGroup): ITableRow => { + const total = this.totalEntryColumnAccessors(); + const meta = { + rowTypes: [ROW_TYPE.TOTAL], + }; + return tableRowMapper(group, total, meta); + }; + + /** + * Maps the entry group to table rows. + * @param {IJournalReportEntriesGroup} group - + * @returns {ITableRow} + */ + private groupMapper = (group: IJournalReportEntriesGroup): ITableRow[] => { + const firstRow = this.firstEntryGroupMapper(group); + const lastRows = this.entriesMapper(group); + const totalRow = this.totalEntryMapper(group); + + return [firstRow, ...lastRows, totalRow]; + }; + + /** + * Maps the given group entries to table rows. + * @param {IJournalReportEntriesGroup[]} entries - + * @returns {ITableRow[]} + */ + private groupsMapper = ( + entries: IJournalReportEntriesGroup[] + ): ITableRow[] => { + return R.compose(R.flatten, R.map(this.groupMapper))(entries); }; /** @@ -80,7 +182,7 @@ export class JournalSheetTable extends R.compose( * @returns {ITableRow[]} */ public tableData(): ITableRow[] { - return R.compose(this.entriesGroupsMapper)(this.data); + return R.compose(this.groupsMapper)(this.data); } /** diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/types.ts b/packages/server/src/services/FinancialStatements/JournalSheet/types.ts new file mode 100644 index 000000000..f71970251 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/types.ts @@ -0,0 +1,6 @@ + + +enum ROW_TYPE { + ENTRY = 'ENTRY', + TOTAL = 'TOTAL' +}; \ No newline at end of file