diff --git a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index ac3f002a7..c86da8eae 100644 --- a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -2,15 +2,16 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { Inject, Service } from 'typedi'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; -import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService'; import BaseFinancialReportController from './BaseFinancialReportController'; import { AbilitySubject, ReportsAction } from '@/interfaces'; import CheckPolicies from '@/api/middleware/CheckPolicies'; +import { ACCEPT_TYPE } from '@/interfaces/Http'; +import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication'; @Service() export default class GeneralLedgerReportController extends BaseFinancialReportController { @Inject() - generalLedgetService: GeneralLedgerService; + private generalLedgerApplication: GeneralLedgerApplication; /** * Router constructor. @@ -61,20 +62,43 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo * @param {Response} res - */ async generalLedger(req: Request, res: Response, next: NextFunction) { - const { tenantId, settings } = req; + const { tenantId } = req; const filter = this.matchedQueryData(req); + const accept = this.accepts(req); - try { - const { data, query, meta } = - await this.generalLedgetService.generalLedger(tenantId, filter); - - return res.status(200).send({ - meta: this.transfromToResponse(meta), - data: this.transfromToResponse(data), - query: this.transfromToResponse(query), - }); - } catch (error) { - next(error); + const acceptType = accept.types([ + ACCEPT_TYPE.APPLICATION_JSON, + ACCEPT_TYPE.APPLICATION_JSON_TABLE, + ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_CSV, + ]); + // Retrieves the table format. + if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { + const table = await this.generalLedgerApplication.table(tenantId, filter); + + return res.status(200).send(table); + // Retrieves the csv format. + } else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { + const buffer = await this.generalLedgerApplication.csv(tenantId, filter); + + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(buffer); + // Retrieves the xlsx format. + } else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { + const buffer = await this.generalLedgerApplication.xlsx(tenantId, filter); + + res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); + return res.send(buffer); + // Retrieves the json format. + } else { + const sheet = await this.generalLedgerApplication.sheet(tenantId, filter); + return res.status(200).send(sheet); } } } diff --git a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts index ebd6074f4..871bc9af8 100644 --- a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -3,14 +3,15 @@ import { Request, Response, Router, NextFunction } from 'express'; import { castArray } from 'lodash'; import { query, oneOf } from 'express-validator'; import BaseFinancialReportController from './BaseFinancialReportController'; -import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService'; import { AbilitySubject, ReportsAction } from '@/interfaces'; import CheckPolicies from '@/api/middleware/CheckPolicies'; +import { ACCEPT_TYPE } from '@/interfaces/Http'; +import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication'; @Service() export default class JournalSheetController extends BaseFinancialReportController { @Inject() - journalService: JournalSheetService; + private journalSheetApp: JournalSheetApplication; /** * Router constructor. @@ -57,28 +58,49 @@ export default class JournalSheetController extends BaseFinancialReportControlle * @param {Request} req - * @param {Response} res - */ - async journal(req: Request, res: Response, next: NextFunction) { - const { tenantId, settings } = req; + private async journal(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; let filter = this.matchedQueryData(req); filter = { ...filter, accountsIds: castArray(filter.accountsIds), }; + const accept = this.accepts(req); + const acceptType = accept.types([ + ACCEPT_TYPE.APPLICATION_JSON, + ACCEPT_TYPE.APPLICATION_JSON_TABLE, + ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_CSV, + ]); - try { - const { data, query, meta } = await this.journalService.journalSheet( - tenantId, - filter + // Retrieves the json table format. + if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { + const table = await this.journalSheetApp.table(tenantId, filter); + return res.status(200).send(table); + // Retrieves the csv format. + } else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { + const buffer = await this.journalSheetApp.csv(tenantId, filter); + + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(buffer); + // Retrieves the xlsx format. + } else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { + const buffer = await this.journalSheetApp.xlsx(tenantId, filter); + + res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); + return res.send(buffer); + // Retrieves the json format. + } else { + const sheet = await this.journalSheetApp.sheet(tenantId, filter); - return res.status(200).send({ - data: this.transfromToResponse(data), - query: this.transfromToResponse(query), - meta: this.transfromToResponse(meta), - }); - } catch (error) { - next(error); + return res.status(200).send(sheet); } } } diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts index 0bef1e60d..20e61fe3a 100644 --- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts @@ -531,7 +531,6 @@ export default class PaymentReceivesController extends BaseController { * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns */ public sendPaymentReceiveByMail = async ( req: Request, @@ -546,6 +545,7 @@ export default class PaymentReceivesController extends BaseController { includeOptionals: false, } ); + try { await this.paymentReceiveApplication.notifyPaymentByMail( tenantId, diff --git a/packages/server/src/interfaces/GeneralLedgerSheet.ts b/packages/server/src/interfaces/GeneralLedgerSheet.ts index bf1662086..6b863a192 100644 --- a/packages/server/src/interfaces/GeneralLedgerSheet.ts +++ b/packages/server/src/interfaces/GeneralLedgerSheet.ts @@ -1,3 +1,4 @@ +import { IFinancialTable } from "./Table"; export interface IGeneralLedgerSheetQuery { @@ -36,6 +37,7 @@ export interface IGeneralLedgerSheetAccountTransaction { referenceType?: string, date: Date|string, + dateFormatted: string; }; export interface IGeneralLedgerSheetAccountBalance { @@ -56,6 +58,8 @@ export interface IGeneralLedgerSheetAccount { closingBalance: IGeneralLedgerSheetAccountBalance, } +export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[]; + export interface IAccountTransaction { id: number, index: number, @@ -78,4 +82,11 @@ export interface IGeneralLedgerMeta { isCostComputeRunning: boolean, organizationName: string, baseCurrency: string, -}; \ No newline at end of file + fromDate: string; + toDate: string; +}; + +export interface IGeneralLedgerTableData extends IFinancialTable { + meta: IGeneralLedgerMeta; + query: IGeneralLedgerSheetQuery; +} \ No newline at end of file diff --git a/packages/server/src/interfaces/JournalReport.ts b/packages/server/src/interfaces/JournalReport.ts index 9786e1634..c003cccf0 100644 --- a/packages/server/src/interfaces/JournalReport.ts +++ b/packages/server/src/interfaces/JournalReport.ts @@ -1,36 +1,52 @@ import { IJournalEntry } from './Journal'; +import { IFinancialTable } from './Table'; export interface IJournalReportQuery { - fromDate: Date | string, - toDate: Date | string, + fromDate: Date | string; + toDate: Date | string; numberFormat: { - noCents: boolean, - divideOn1000: boolean, - }, - transactionType: string, - transactionId: string, - - accountsIds: number | number[], - fromRange: number, - toRange: number, + noCents: boolean; + divideOn1000: boolean; + }; + transactionType: string; + transactionId: string; + + accountsIds: number | number[]; + fromRange: number; + toRange: number; } export interface IJournalReportEntriesGroup { - id: string, - entries: IJournalEntry[], - currencyCode: string, - credit: number, - debit: number, - formattedCredit: string, - formattedDebit: string, + id: string; + date: Date; + dateFormatted: string; + entries: IJournalEntry[]; + currencyCode: string; + credit: number; + debit: number; + formattedCredit: string; + formattedDebit: string; } export interface IJournalReport { - entries: IJournalReportEntriesGroup[], + entries: IJournalReportEntriesGroup[]; } export interface IJournalSheetMeta { - isCostComputeRunning: boolean, - organizationName: string, - baseCurrency: string, -} \ No newline at end of file + isCostComputeRunning: boolean; + organizationName: string; + baseCurrency: string; +} + +export interface IJournalTable extends IFinancialTable { + query: IJournalReportQuery; + meta: IJournalSheetMeta; +} + +export type IJournalTableData = IJournalReportEntriesGroup[]; + +export interface IJournalSheet { + data: IJournalTableData; + query: IJournalReportQuery; + meta: IJournalSheetMeta; +} diff --git a/packages/server/src/models/Contact.ts b/packages/server/src/models/Contact.ts index d63a2ea60..69661f639 100644 --- a/packages/server/src/models/Contact.ts +++ b/packages/server/src/models/Contact.ts @@ -2,6 +2,9 @@ import { Model } from 'objection'; import TenantModel from 'models/TenantModel'; export default class Contact extends TenantModel { + email: string; + displayName: string; + /** * Table name */ diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts index 512ed37d7..5e12e9078 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,8 +89,10 @@ export default class GeneralLedgerSheet extends FinancialSheet { const newEntry = { date: entry.date, + dateFormatted: moment(entry.date).format('YYYY MMM DD'), entryId: entry.id, + transactionNumber: entry.transactionNumber, referenceType: entry.referenceType, referenceId: entry.referenceId, referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted), diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts new file mode 100644 index 000000000..924b0da8c --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts @@ -0,0 +1,66 @@ +import { Inject } from 'typedi'; +import { + IGeneralLedgerSheetQuery, + IGeneralLedgerTableData, +} from '@/interfaces'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; +import { GeneralLedgerExportInjectable } from './GeneralLedgerExport'; +import { GeneralLedgerService } from './GeneralLedgerService'; + +export class GeneralLedgerApplication { + @Inject() + private GLTable: GeneralLedgerTableInjectable; + + @Inject() + private GLExport: GeneralLedgerExportInjectable; + + @Inject() + private GLSheet: GeneralLedgerService; + + /** + * Retrieves the G/L sheet in json format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + */ + public sheet(tenantId: number, query: IGeneralLedgerSheetQuery) { + return this.GLSheet.generalLedger(tenantId, query); + } + + /** + * Retrieves the G/L sheet in table format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public table( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + return this.GLTable.table(tenantId, query); + } + + /** + * Retrieves the G/L sheet in xlsx format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {} + */ + public xlsx( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + return this.GLExport.xlsx(tenantId, query); + } + + /** + * Retrieves the G/L sheet in csv format. + * @param {number} tenantId - + * @param {IGeneralLedgerSheetQuery} query - + */ + public csv( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + return this.GLExport.csv(tenantId, query); + } +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerExport.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerExport.ts new file mode 100644 index 000000000..f05c817c2 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerExport.ts @@ -0,0 +1,43 @@ +import { IGeneralLedgerSheetQuery } from '@/interfaces'; +import { TableSheet } from '@/lib/Xlsx/TableSheet'; +import { Inject, Service } from 'typedi'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; + +@Service() +export class GeneralLedgerExportInjectable { + @Inject() + private generalLedgerTable: GeneralLedgerTableInjectable; + + /** + * Retrieves the general ledger sheet in XLSX format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public async xlsx(tenantId: number, query: IGeneralLedgerSheetQuery) { + const table = await this.generalLedgerTable.table(tenantId, query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToXLSX(); + + return tableSheet.convertToBuffer(tableCsv, 'xlsx'); + } + + /** + * Retrieves the general ledger sheet in CSV format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public async csv( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + const table = await this.generalLedgerTable.table(tenantId, query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts index 012a05b94..100239e39 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts @@ -15,7 +15,7 @@ const ERRORS = { }; @Service() -export default class GeneralLedgerService { +export class GeneralLedgerService { @Inject() tenancy: TenancyService; @@ -64,7 +64,7 @@ export default class GeneralLedgerService { * @param {number} tenantId - * @returns {IGeneralLedgerMeta} */ - reportMetadata(tenantId: number): IGeneralLedgerMeta { + reportMetadata(tenantId: number, filter): IGeneralLedgerMeta { const settings = this.tenancy.settings(tenantId); const isCostComputeRunning = this.inventoryService @@ -78,11 +78,15 @@ export default class GeneralLedgerService { group: 'organization', key: 'base_currency', }); + const fromDate = moment(filter.fromDate).format('YYYY MMM DD'); + const toDate = moment(filter.toDate).format('YYYY MMM DD'); return { isCostComputeRunning: parseBoolean(isCostComputeRunning, false), organizationName, - baseCurrency + baseCurrency, + fromDate, + toDate }; } @@ -166,7 +170,7 @@ export default class GeneralLedgerService { return { data: reportData, query: filter, - meta: this.reportMetadata(tenantId), + meta: this.reportMetadata(tenantId, filter), }; } } diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts new file mode 100644 index 000000000..1820ab095 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTable.ts @@ -0,0 +1,256 @@ +import * as R from 'ramda'; +import { + IColumnMapperMeta, + IGeneralLedgerMeta, + IGeneralLedgerSheetAccount, + IGeneralLedgerSheetAccountTransaction, + IGeneralLedgerSheetData, + IGeneralLedgerSheetQuery, + ITableColumn, + ITableColumnAccessor, + ITableRow, +} from '@/interfaces'; +import FinancialSheet from '../FinancialSheet'; +import { FinancialSheetStructure } from '../FinancialSheetStructure'; +import { FinancialTable } from '../FinancialTable'; +import { tableRowMapper } from '@/utils'; +import { ROW_TYPE } from './utils'; + +export class GeneralLedgerTable extends R.compose( + FinancialTable, + FinancialSheetStructure +)(FinancialSheet) { + private data: IGeneralLedgerSheetData; + private query: IGeneralLedgerSheetQuery; + private meta: IGeneralLedgerMeta; + + /** + * Creates an instance of `GeneralLedgerTable`. + * @param {IGeneralLedgerSheetData} data + * @param {IGeneralLedgerSheetQuery} query + */ + constructor( + data: IGeneralLedgerSheetData, + query: IGeneralLedgerSheetQuery, + meta: IGeneralLedgerMeta + ) { + super(); + + this.data = data; + this.query = query; + this.meta = meta; + } + + /** + * Retrieves the common table accessors. + * @returns {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: 'closingBalance.formattedAmount' }, + ]; + } + + /** + * Retrieves the transaction column accessors. + * @returns {ITableColumnAccessor[]} + */ + private transactionColumnAccessors(): ITableColumnAccessor[] { + return [ + { key: 'date', accessor: 'dateFormatted' }, + { key: 'account_name', accessor: 'account.name' }, + { key: 'reference_type', accessor: 'referenceTypeFormatted' }, + { key: 'reference_number', accessor: 'transactionNumber' }, + { key: 'description', accessor: 'note' }, + { key: 'credit', accessor: 'formattedCredit' }, + { key: 'debit', accessor: 'formattedDebit' }, + { key: 'amount', accessor: 'formattedAmount' }, + { key: 'running_balance', accessor: 'formattedRunningBalance' }, + ]; + } + + /** + * Retrieves the opening row column accessors. + * @returns {ITableRowIColumnMapperMeta[]} + */ + private openingBalanceColumnsAccessors(): IColumnMapperMeta[] { + return [ + { key: 'date', value: this.meta.fromDate }, + { key: 'account_name', value: 'Opening Balance' }, + { 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: 'openingBalance.formattedAmount' }, + { key: 'running_balance', accessor: 'openingBalance.formattedAmount' }, + ]; + } + + /** + * Closing balance row column accessors. + * @returns {ITableColumnAccessor[]} + */ + private closingBalanceColumnAccessors(): IColumnMapperMeta[] { + return [ + { key: 'date', value: this.meta.toDate }, + { key: 'account_name', value: 'Closing Balance' }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: '_empty_' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'closingBalance.formattedAmount' }, + { key: 'running_balance', accessor: 'closingBalance.formattedAmount' }, + ]; + } + + /** + * Retrieves the common table columns. + * @returns {ITableColumn[]} + */ + private commonColumns(): ITableColumn[] { + return [ + { key: 'date', label: 'Date' }, + { key: 'account_name', label: 'Account Name' }, + { key: 'reference_type', label: 'Transaction Type' }, + { key: 'reference_number', label: 'Transaction #' }, + { 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 = R.curry( + ( + account: IGeneralLedgerSheetAccount, + transaction: IGeneralLedgerSheetAccountTransaction + ): ITableRow => { + const columns = this.transactionColumnAccessors(); + const data = { ...transaction, account }; + const meta = { + rowTypes: [ROW_TYPE.TRANSACTION], + }; + return tableRowMapper(data, columns, meta); + } + ); + + /** + * Maps the given transactions nodes to table rows. + * @param {IGeneralLedgerSheetAccountTransaction[]} transactions + * @returns {ITableRow[]} + */ + private transactionsMapper = ( + account: IGeneralLedgerSheetAccount + ): ITableRow[] => { + const transactionMapper = this.transactionMapper(account); + + return R.map(transactionMapper)(account.transactions); + }; + + /** + * Maps the given account node to opening balance table row. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private openingBalanceMapper = ( + account: IGeneralLedgerSheetAccount + ): ITableRow => { + const columns = this.openingBalanceColumnsAccessors(); + const meta = { + rowTypes: [ROW_TYPE.OPENING_BALANCE], + }; + return tableRowMapper(account, columns, meta); + }; + + /** + * Maps the given account node to closing balance table row. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => { + const columns = this.closingBalanceColumnAccessors(); + const meta = { + rowTypes: [ROW_TYPE.CLOSING_BALANCE], + }; + return tableRowMapper(account, columns, meta); + }; + + /** + * Maps the given account node to transactions table rows. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow[]} + */ + private transactionsNode = ( + account: IGeneralLedgerSheetAccount + ): ITableRow[] => { + const openingBalance = this.openingBalanceMapper(account); + const transactions = this.transactionsMapper(account); + const closingBalance = this.closingBalanceMapper(account); + + return R.when( + R.always(R.not(R.isEmpty(transactions))), + R.prepend(openingBalance) + )([...transactions, closingBalance]) as ITableRow[]; + }; + + /** + * Maps the given account node to the table rows. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private accountMapper = (account: IGeneralLedgerSheetAccount): ITableRow => { + const columns = this.accountColumnsAccessors(); + const transactions = this.transactionsNode(account); + const meta = { + rowTypes: [ROW_TYPE.ACCOUNT], + }; + const row = tableRowMapper(account, columns, meta); + + return R.assoc('children', transactions)(row); + }; + + /** + * Maps the given account node to table rows. + * @param {IGeneralLedgerSheetAccount[]} accounts + * @returns {ITableRow[]} + */ + private accountsMapper = ( + accounts: IGeneralLedgerSheetAccount[] + ): ITableRow[] => { + return this.mapNodesDeep(accounts, this.accountMapper); + }; + + /** + * Retrieves the table rows. + * @returns {ITableRow[]} + */ + public tableRows(): ITableRow[] { + return R.compose(this.accountsMapper)(this.data); + } + + /** + * Retrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + const columns = this.commonColumns(); + + return R.compose(this.tableColumnsCellIndexing)(columns); + } +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTableInjectable.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTableInjectable.ts new file mode 100644 index 000000000..c830c29c2 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerTableInjectable.ts @@ -0,0 +1,45 @@ +import { + IGeneralLedgerSheetQuery, + IGeneralLedgerTableData, +} from '@/interfaces'; +import { Inject, Service } from 'typedi'; +import { GeneralLedgerService } from './GeneralLedgerService'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { GeneralLedgerTable } from './GeneralLedgerTable'; + +@Service() +export class GeneralLedgerTableInjectable { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private GLSheet: GeneralLedgerService; + + /** + * Retrieves the G/L table. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public async table( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + const { + data: sheetData, + query: sheetQuery, + meta: sheetMeta, + } = await this.GLSheet.generalLedger(tenantId, query); + + const table = new GeneralLedgerTable(sheetData, sheetQuery, sheetMeta); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableRows(), + }, + query: sheetQuery, + meta: sheetMeta, + }; + } +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/utils.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/utils.ts new file mode 100644 index 000000000..07418ae37 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/utils.ts @@ -0,0 +1,6 @@ +export enum ROW_TYPE { + ACCOUNT = 'ACCOUNT', + OPENING_BALANCE = 'OPENING_BALANCE', + TRANSACTION = 'TRANSACTION', + CLOSING_BALANCE = 'CLOSING_BALANCE', +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts index ee184a5a1..89725db3b 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 MMM 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/JournalSheetApplication.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts new file mode 100644 index 000000000..4c403ff58 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts @@ -0,0 +1,59 @@ +import { Inject } from 'typedi'; +import { JournalSheetService } from './JournalSheetService'; +import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; +import { IJournalReportQuery, IJournalTable } from '@/interfaces'; +import { JournalSheetExportInjectable } from './JournalSheetExport'; + +export class JournalSheetApplication { + @Inject() + private journalSheetTable: JournalSheetTableInjectable; + + @Inject() + private journalSheet: JournalSheetService; + + @Inject() + private journalExport: JournalSheetExportInjectable; + + /** + * Retrieves the journal sheet. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {} + */ + public sheet(tenantId: number, query: IJournalReportQuery) { + return this.journalSheet.journalSheet(tenantId, query); + } + + /** + * Retrieves the journal sheet in table format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public table( + tenantId: number, + query: IJournalReportQuery + ): Promise { + return this.journalSheetTable.table(tenantId, query); + } + + /** + * Retrieves the journal sheet in xlsx format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns + */ + public xlsx(tenantId: number, query: IJournalReportQuery) { + return this.journalExport.xlsx(tenantId, query); + } + + /** + * Retrieves the journal sheet in csv format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns + */ + public csv(tenantId: number, query: IJournalReportQuery) { + return this.journalExport.csv(tenantId, query); + } +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetExport.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetExport.ts new file mode 100644 index 000000000..815c0a308 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetExport.ts @@ -0,0 +1,43 @@ +import { Inject, Service } from 'typedi'; +import { TableSheet } from '@/lib/Xlsx/TableSheet'; +import { IJournalReportQuery } from '@/interfaces'; +import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; + +@Service() +export class JournalSheetExportInjectable { + @Inject() + private journalSheetTable: JournalSheetTableInjectable; + + /** + * Retrieves the trial balance sheet in XLSX format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public async xlsx(tenantId: number, query: IJournalReportQuery) { + const table = await this.journalSheetTable.table(tenantId, query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToXLSX(); + + return tableSheet.convertToBuffer(tableCsv, 'xlsx'); + } + + /** + * Retrieves the trial balance sheet in CSV format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public async csv( + tenantId: number, + query: IJournalReportQuery + ): Promise { + const table = await this.journalSheetTable.table(tenantId, query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts index fe0a071ff..69a319ff3 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts @@ -1,24 +1,25 @@ 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'; import InventoryService from '@/services/Inventory/Inventory'; -import { parseBoolean, transformToMap } from 'utils'; import { Tenant } from '@/system/models'; +import { parseBoolean, transformToMap } from 'utils'; @Service() -export default class JournalSheetService { +export class JournalSheetService { @Inject() - tenancy: TenancyService; + private tenancy: TenancyService; @Inject() - inventoryService: InventoryService; - - @Inject('logger') - logger: any; + private inventoryService: InventoryService; /** * Default journal sheet filter queyr. @@ -67,9 +68,13 @@ export default 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); @@ -80,11 +85,6 @@ export default class JournalSheetService { ...this.defaultQuery, ...query, }; - this.logger.info('[journal] trying to calculate the report.', { - tenantId, - filter, - }); - const tenant = await Tenant.query() .findById(tenantId) .withGraphFetched('metadata'); diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts new file mode 100644 index 000000000..724e2ec2b --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTable.ts @@ -0,0 +1,232 @@ +import * as R from 'ramda'; +import { first } from 'lodash'; +import { + IColumnMapperMeta, + IJournalEntry, + IJournalReportEntriesGroup, + IJournalReportQuery, + IJournalTableData, + ITableColumn, + ITableColumnAccessor, + ITableRow, +} from '@/interfaces'; +import { tableRowMapper } from '@/utils'; +import { FinancialTable } from '../FinancialTable'; +import { FinancialSheetStructure } from '../FinancialSheetStructure'; +import FinancialSheet from '../FinancialSheet'; +import { ROW_TYPE } from './types'; + +export class JournalSheetTable extends R.compose( + FinancialTable, + FinancialSheetStructure +)(FinancialSheet) { + private data: IJournalTableData; + 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; + this.query = query; + this.i18n = i18n; + } + + /** + * Retrieves the common table accessors. + * @returns {ITableColumnAccessor[]} + */ + private groupColumnsAccessors = (): ITableColumnAccessor[] => { + return [ + { key: 'date', accessor: 'dateFormatted' }, + { key: 'transaction_type', accessor: 'referenceTypeFormatted' }, + { key: 'transaction_number', accessor: 'entry.transactionNumber' }, + { key: 'description', accessor: 'entry.note' }, + { 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: 'transactionNumber' }, + { key: 'description', accessor: 'note' }, + { key: 'account_code', accessor: 'accountCode' }, + { key: 'account_name', accessor: 'accountName' }, + { key: 'credit', accessor: 'formattedCredit' }, + { key: 'debit', accessor: 'formattedDebit' }, + ]; + }; + + /** + * Retrieves the total entry column accessors. + * @returns {ITableColumnAccessor[]} + */ + 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 total entry column accessors. + * @returns {IColumnMapperMeta[]} + */ + private blankEnrtyColumnAccessors = (): IColumnMapperMeta[] => { + return [ + { key: 'date', value: '' }, + { key: 'transaction_type', value: '' }, + { key: 'transaction_number', value: '' }, + { key: 'description', value: '' }, + { key: 'account_code', value: '' }, + { key: 'account_name', value: '' }, + { key: 'credit', value: '' }, + { key: 'debit', value: '' }, + ]; + }; + + /** + * Retrieves the common columns. + * @returns {ITableColumn[]} + */ + private commonColumns(): ITableColumn[] { + return [ + { key: 'date', label: 'Date' }, + { 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 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 = (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 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); + }; + + /** + * Retrieves the blank entry row. + * @returns {ITableRow} + */ + private blankEntryMapper = (): ITableRow => { + const columns = this.blankEnrtyColumnAccessors(); + const meta = {}; + return tableRowMapper({} as IJournalEntry, columns, 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); + const blankRow = this.blankEntryMapper(); + + return [firstRow, ...lastRows, totalRow, blankRow]; + }; + + /** + * 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); + }; + + /** + * Retrieves the table data rows. + * @returns {ITableRow[]} + */ + public tableData(): ITableRow[] { + return R.compose(this.groupsMapper)(this.data); + } + + /** + * Retrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + const columns = this.commonColumns(); + + return R.compose(this.tableColumnsCellIndexing)(columns); + } +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTableInjectable.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTableInjectable.ts new file mode 100644 index 000000000..0754d78f8 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetTableInjectable.ts @@ -0,0 +1,39 @@ +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { Inject } from 'typedi'; +import { JournalSheetService } from './JournalSheetService'; +import { IJournalReportQuery, IJournalTable } from '@/interfaces'; +import { JournalSheetTable } from './JournalSheetTable'; + +export class JournalSheetTableInjectable { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private journalSheetService: JournalSheetService; + + /** + * Retrieves the journal sheet in table format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public async table( + tenantId: number, + query: IJournalReportQuery + ): Promise { + const journal = await this.journalSheetService.journalSheet( + tenantId, + query + ); + const table = new JournalSheetTable(journal.data, journal.query, {}); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableData(), + }, + query: journal.query, + meta: journal.meta, + }; + } +} 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..6eff84957 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/types.ts @@ -0,0 +1,5 @@ + +export enum ROW_TYPE { + ENTRY = 'ENTRY', + TOTAL = 'TOTAL' +}; \ No newline at end of file diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts index b5389a8a0..f16db0172 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts @@ -7,6 +7,7 @@ import { DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, } from './constants'; +import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; @Service() export class SendInvoiceMailReminder { @@ -66,10 +67,10 @@ export class SendInvoiceMailReminder { ) { const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId); - const messageOpts = { - ...localMessageOpts, - ...messageOptions, - }; + const messageOpts = parseAndValidateMailOptions( + localMessageOpts, + messageOptions + ); const mail = new Mail() .setSubject(messageOpts.subject) .setTo(messageOpts.to) diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts index 236a33758..b29570d42 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts @@ -1,5 +1,4 @@ import Container, { Service } from 'typedi'; -import events from '@/subscribers/events'; import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification'; @Service() @@ -22,8 +21,6 @@ export class PaymentReceiveMailNotificationJob { const { tenantId, paymentReceiveId, messageDTO } = job.attrs.data; const paymentMail = Container.get(SendPaymentReceiveMailNotification); - console.log(tenantId, paymentReceiveId, messageDTO); - try { await paymentMail.sendMail(tenantId, paymentReceiveId, messageDTO); done(); diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts index 459d9c62e..d4c87df29 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts @@ -177,6 +177,7 @@ export class SaleReceiptApplication { * Sends the receipt mail of the given sale receipt. * @param {number} tenantId * @param {number} saleReceiptId + * @param {SaleReceiptMailOptsDTO} messageOpts * @returns {Promise} */ public sendSaleReceiptMail( diff --git a/packages/webapp/src/components/Dashboard/Dashboard.tsx b/packages/webapp/src/components/Dashboard/Dashboard.tsx index 2637128cf..467963849 100644 --- a/packages/webapp/src/components/Dashboard/Dashboard.tsx +++ b/packages/webapp/src/components/Dashboard/Dashboard.tsx @@ -51,8 +51,8 @@ export default function Dashboard() { - + diff --git a/packages/webapp/src/containers/Accounts/AccountsDataTable.tsx b/packages/webapp/src/containers/Accounts/AccountsDataTable.tsx index dfa4b90be..edcfe2e18 100644 --- a/packages/webapp/src/containers/Accounts/AccountsDataTable.tsx +++ b/packages/webapp/src/containers/Accounts/AccountsDataTable.tsx @@ -32,7 +32,7 @@ function AccountsDataTable({ // #withAlertsDialog openAlert, - // #withDial + // #withDialog openDialog, // #withDrawerActions diff --git a/packages/webapp/src/containers/Authentication/Authentication.tsx b/packages/webapp/src/containers/Authentication/Authentication.tsx index 8d2887849..8b05570c4 100644 --- a/packages/webapp/src/containers/Authentication/Authentication.tsx +++ b/packages/webapp/src/containers/Authentication/Authentication.tsx @@ -8,9 +8,9 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'; import authenticationRoutes from '@/routes/authentication'; import { Icon, FormattedMessage as T } from '@/components'; import { useIsAuthenticated } from '@/hooks/state'; +import { AuthMetaBootProvider } from './AuthMetaBoot'; import '@/style/pages/Authentication/Auth.scss'; -import { AuthMetaBootProvider } from './AuthMetaBoot'; export function Authentication() { const to = { pathname: '/' }; diff --git a/packages/webapp/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.tsx index 971da31d9..e450c78bb 100644 --- a/packages/webapp/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.tsx +++ b/packages/webapp/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.tsx @@ -12,6 +12,7 @@ import { import classNames from 'classnames'; import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components'; +import { GeneralLedgerSheetExportMenu } from './components'; import { useGeneralLedgerContext } from './GeneralLedgerProvider'; import { compose } from '@/utils'; @@ -84,11 +85,18 @@ function GeneralLedgerActionsBar({ icon={} text={} /> -