mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
Merge pull request #303 from bigcapitalhq/journal-sheet-export
Export general ledger & Journal to CSV and XLSX
This commit is contained in:
@@ -2,15 +2,16 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { query, ValidationChain } from 'express-validator';
|
import { query, ValidationChain } from 'express-validator';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import GeneralLedgerService from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerService';
|
|
||||||
import BaseFinancialReportController from './BaseFinancialReportController';
|
import BaseFinancialReportController from './BaseFinancialReportController';
|
||||||
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
import { GeneralLedgerApplication } from '@/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GeneralLedgerReportController extends BaseFinancialReportController {
|
export default class GeneralLedgerReportController extends BaseFinancialReportController {
|
||||||
@Inject()
|
@Inject()
|
||||||
generalLedgetService: GeneralLedgerService;
|
private generalLedgerApplication: GeneralLedgerApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -61,20 +62,43 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async generalLedger(req: Request, res: Response, next: NextFunction) {
|
async generalLedger(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId, settings } = req;
|
const { tenantId } = req;
|
||||||
const filter = this.matchedQueryData(req);
|
const filter = this.matchedQueryData(req);
|
||||||
|
const accept = this.accepts(req);
|
||||||
|
|
||||||
try {
|
const acceptType = accept.types([
|
||||||
const { data, query, meta } =
|
ACCEPT_TYPE.APPLICATION_JSON,
|
||||||
await this.generalLedgetService.generalLedger(tenantId, filter);
|
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({
|
return res.status(200).send(table);
|
||||||
meta: this.transfromToResponse(meta),
|
// Retrieves the csv format.
|
||||||
data: this.transfromToResponse(data),
|
} else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) {
|
||||||
query: this.transfromToResponse(query),
|
const buffer = await this.generalLedgerApplication.csv(tenantId, filter);
|
||||||
});
|
|
||||||
} catch (error) {
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
next(error);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import { Request, Response, Router, NextFunction } from 'express';
|
|||||||
import { castArray } from 'lodash';
|
import { castArray } from 'lodash';
|
||||||
import { query, oneOf } from 'express-validator';
|
import { query, oneOf } from 'express-validator';
|
||||||
import BaseFinancialReportController from './BaseFinancialReportController';
|
import BaseFinancialReportController from './BaseFinancialReportController';
|
||||||
import JournalSheetService from '@/services/FinancialStatements/JournalSheet/JournalSheetService';
|
|
||||||
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
import { AbilitySubject, ReportsAction } from '@/interfaces';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
|
import { ACCEPT_TYPE } from '@/interfaces/Http';
|
||||||
|
import { JournalSheetApplication } from '@/services/FinancialStatements/JournalSheet/JournalSheetApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class JournalSheetController extends BaseFinancialReportController {
|
export default class JournalSheetController extends BaseFinancialReportController {
|
||||||
@Inject()
|
@Inject()
|
||||||
journalService: JournalSheetService;
|
private journalSheetApp: JournalSheetApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -57,28 +58,49 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
|||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
*/
|
*/
|
||||||
async journal(req: Request, res: Response, next: NextFunction) {
|
private async journal(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId, settings } = req;
|
const { tenantId } = req;
|
||||||
let filter = this.matchedQueryData(req);
|
let filter = this.matchedQueryData(req);
|
||||||
|
|
||||||
filter = {
|
filter = {
|
||||||
...filter,
|
...filter,
|
||||||
accountsIds: castArray(filter.accountsIds),
|
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 {
|
// Retrieves the json table format.
|
||||||
const { data, query, meta } = await this.journalService.journalSheet(
|
if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) {
|
||||||
tenantId,
|
const table = await this.journalSheetApp.table(tenantId, filter);
|
||||||
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({
|
return res.status(200).send(sheet);
|
||||||
data: this.transfromToResponse(data),
|
|
||||||
query: this.transfromToResponse(query),
|
|
||||||
meta: this.transfromToResponse(meta),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -531,7 +531,6 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
public sendPaymentReceiveByMail = async (
|
public sendPaymentReceiveByMail = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -546,6 +545,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
includeOptionals: false,
|
includeOptionals: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveApplication.notifyPaymentByMail(
|
await this.paymentReceiveApplication.notifyPaymentByMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { IFinancialTable } from "./Table";
|
||||||
|
|
||||||
|
|
||||||
export interface IGeneralLedgerSheetQuery {
|
export interface IGeneralLedgerSheetQuery {
|
||||||
@@ -36,6 +37,7 @@ export interface IGeneralLedgerSheetAccountTransaction {
|
|||||||
referenceType?: string,
|
referenceType?: string,
|
||||||
|
|
||||||
date: Date|string,
|
date: Date|string,
|
||||||
|
dateFormatted: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IGeneralLedgerSheetAccountBalance {
|
export interface IGeneralLedgerSheetAccountBalance {
|
||||||
@@ -56,6 +58,8 @@ export interface IGeneralLedgerSheetAccount {
|
|||||||
closingBalance: IGeneralLedgerSheetAccountBalance,
|
closingBalance: IGeneralLedgerSheetAccountBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||||
|
|
||||||
export interface IAccountTransaction {
|
export interface IAccountTransaction {
|
||||||
id: number,
|
id: number,
|
||||||
index: number,
|
index: number,
|
||||||
@@ -78,4 +82,11 @@ export interface IGeneralLedgerMeta {
|
|||||||
isCostComputeRunning: boolean,
|
isCostComputeRunning: boolean,
|
||||||
organizationName: string,
|
organizationName: string,
|
||||||
baseCurrency: string,
|
baseCurrency: string,
|
||||||
|
fromDate: string;
|
||||||
|
toDate: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IGeneralLedgerTableData extends IFinancialTable {
|
||||||
|
meta: IGeneralLedgerMeta;
|
||||||
|
query: IGeneralLedgerSheetQuery;
|
||||||
|
}
|
||||||
@@ -1,36 +1,52 @@
|
|||||||
import { IJournalEntry } from './Journal';
|
import { IJournalEntry } from './Journal';
|
||||||
|
import { IFinancialTable } from './Table';
|
||||||
|
|
||||||
export interface IJournalReportQuery {
|
export interface IJournalReportQuery {
|
||||||
fromDate: Date | string,
|
fromDate: Date | string;
|
||||||
toDate: Date | string,
|
toDate: Date | string;
|
||||||
numberFormat: {
|
numberFormat: {
|
||||||
noCents: boolean,
|
noCents: boolean;
|
||||||
divideOn1000: boolean,
|
divideOn1000: boolean;
|
||||||
},
|
};
|
||||||
transactionType: string,
|
transactionType: string;
|
||||||
transactionId: string,
|
transactionId: string;
|
||||||
|
|
||||||
accountsIds: number | number[],
|
accountsIds: number | number[];
|
||||||
fromRange: number,
|
fromRange: number;
|
||||||
toRange: number,
|
toRange: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IJournalReportEntriesGroup {
|
export interface IJournalReportEntriesGroup {
|
||||||
id: string,
|
id: string;
|
||||||
entries: IJournalEntry[],
|
date: Date;
|
||||||
currencyCode: string,
|
dateFormatted: string;
|
||||||
credit: number,
|
entries: IJournalEntry[];
|
||||||
debit: number,
|
currencyCode: string;
|
||||||
formattedCredit: string,
|
credit: number;
|
||||||
formattedDebit: string,
|
debit: number;
|
||||||
|
formattedCredit: string;
|
||||||
|
formattedDebit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IJournalReport {
|
export interface IJournalReport {
|
||||||
entries: IJournalReportEntriesGroup[],
|
entries: IJournalReportEntriesGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IJournalSheetMeta {
|
export interface IJournalSheetMeta {
|
||||||
isCostComputeRunning: boolean,
|
isCostComputeRunning: boolean;
|
||||||
organizationName: string,
|
organizationName: string;
|
||||||
baseCurrency: 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;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,9 @@ import { Model } from 'objection';
|
|||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
|
||||||
export default class Contact extends TenantModel {
|
export default class Contact extends TenantModel {
|
||||||
|
email: string;
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
IContact,
|
IContact,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet.
|
* General ledger sheet.
|
||||||
@@ -88,8 +89,10 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
|
|
||||||
const newEntry = {
|
const newEntry = {
|
||||||
date: entry.date,
|
date: entry.date,
|
||||||
|
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||||
entryId: entry.id,
|
entryId: entry.id,
|
||||||
|
|
||||||
|
transactionNumber: entry.transactionNumber,
|
||||||
referenceType: entry.referenceType,
|
referenceType: entry.referenceType,
|
||||||
referenceId: entry.referenceId,
|
referenceId: entry.referenceId,
|
||||||
referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted),
|
referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted),
|
||||||
|
|||||||
@@ -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<IGeneralLedgerTableData>}
|
||||||
|
*/
|
||||||
|
public table(
|
||||||
|
tenantId: number,
|
||||||
|
query: IGeneralLedgerSheetQuery
|
||||||
|
): Promise<IGeneralLedgerTableData> {
|
||||||
|
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<Buffer> {
|
||||||
|
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<string> {
|
||||||
|
return this.GLExport.csv(tenantId, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Buffer>}
|
||||||
|
*/
|
||||||
|
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<Buffer>}
|
||||||
|
*/
|
||||||
|
public async csv(
|
||||||
|
tenantId: number,
|
||||||
|
query: IGeneralLedgerSheetQuery
|
||||||
|
): Promise<string> {
|
||||||
|
const table = await this.generalLedgerTable.table(tenantId, query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ const ERRORS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class GeneralLedgerService {
|
export class GeneralLedgerService {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ export default class GeneralLedgerService {
|
|||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @returns {IGeneralLedgerMeta}
|
* @returns {IGeneralLedgerMeta}
|
||||||
*/
|
*/
|
||||||
reportMetadata(tenantId: number): IGeneralLedgerMeta {
|
reportMetadata(tenantId: number, filter): IGeneralLedgerMeta {
|
||||||
const settings = this.tenancy.settings(tenantId);
|
const settings = this.tenancy.settings(tenantId);
|
||||||
|
|
||||||
const isCostComputeRunning = this.inventoryService
|
const isCostComputeRunning = this.inventoryService
|
||||||
@@ -78,11 +78,15 @@ export default class GeneralLedgerService {
|
|||||||
group: 'organization',
|
group: 'organization',
|
||||||
key: 'base_currency',
|
key: 'base_currency',
|
||||||
});
|
});
|
||||||
|
const fromDate = moment(filter.fromDate).format('YYYY MMM DD');
|
||||||
|
const toDate = moment(filter.toDate).format('YYYY MMM DD');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
|
||||||
organizationName,
|
organizationName,
|
||||||
baseCurrency
|
baseCurrency,
|
||||||
|
fromDate,
|
||||||
|
toDate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +170,7 @@ export default class GeneralLedgerService {
|
|||||||
return {
|
return {
|
||||||
data: reportData,
|
data: reportData,
|
||||||
query: filter,
|
query: filter,
|
||||||
meta: this.reportMetadata(tenantId),
|
meta: this.reportMetadata(tenantId, filter),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<IGeneralLedgerTableData>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
tenantId: number,
|
||||||
|
query: IGeneralLedgerSheetQuery
|
||||||
|
): Promise<IGeneralLedgerTableData> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export enum ROW_TYPE {
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||||
|
TRANSACTION = 'TRANSACTION',
|
||||||
|
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||||
|
}
|
||||||
@@ -6,8 +6,10 @@ import {
|
|||||||
IJournalReportQuery,
|
IJournalReportQuery,
|
||||||
IJournalReport,
|
IJournalReport,
|
||||||
IContact,
|
IContact,
|
||||||
|
IJournalTableData,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default class JournalSheet extends FinancialSheet {
|
export default class JournalSheet extends FinancialSheet {
|
||||||
readonly tenantId: number;
|
readonly tenantId: number;
|
||||||
@@ -96,6 +98,8 @@ export default class JournalSheet extends FinancialSheet {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
date: groupEntry.date,
|
date: groupEntry.date,
|
||||||
|
dateFormatted: moment(groupEntry.date).format('YYYY MMM DD'),
|
||||||
|
|
||||||
referenceType: groupEntry.referenceType,
|
referenceType: groupEntry.referenceType,
|
||||||
referenceId: groupEntry.referenceId,
|
referenceId: groupEntry.referenceId,
|
||||||
referenceTypeFormatted: this.i18n.__(groupEntry.referenceTypeFormatted),
|
referenceTypeFormatted: this.i18n.__(groupEntry.referenceTypeFormatted),
|
||||||
@@ -131,7 +135,7 @@ export default class JournalSheet extends FinancialSheet {
|
|||||||
* Retrieve journal report.
|
* Retrieve journal report.
|
||||||
* @return {IJournalReport}
|
* @return {IJournalReport}
|
||||||
*/
|
*/
|
||||||
reportData(): IJournalReport {
|
reportData(): IJournalTableData {
|
||||||
return this.entriesWalker(this.journal.entries);
|
return this.entriesWalker(this.journal.entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<IJournalTable>}
|
||||||
|
*/
|
||||||
|
public table(
|
||||||
|
tenantId: number,
|
||||||
|
query: IJournalReportQuery
|
||||||
|
): Promise<IJournalTable> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Buffer>}
|
||||||
|
*/
|
||||||
|
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<Buffer>}
|
||||||
|
*/
|
||||||
|
public async csv(
|
||||||
|
tenantId: number,
|
||||||
|
query: IJournalReportQuery
|
||||||
|
): Promise<string> {
|
||||||
|
const table = await this.journalSheetTable.table(tenantId, query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { IJournalReportQuery, IJournalSheetMeta } from '@/interfaces';
|
import {
|
||||||
|
IJournalReportQuery,
|
||||||
|
IJournalSheet,
|
||||||
|
IJournalSheetMeta,
|
||||||
|
IJournalTableData,
|
||||||
|
} from '@/interfaces';
|
||||||
import JournalSheet from './JournalSheet';
|
import JournalSheet from './JournalSheet';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import Journal from '@/services/Accounting/JournalPoster';
|
import Journal from '@/services/Accounting/JournalPoster';
|
||||||
import InventoryService from '@/services/Inventory/Inventory';
|
import InventoryService from '@/services/Inventory/Inventory';
|
||||||
import { parseBoolean, transformToMap } from 'utils';
|
|
||||||
import { Tenant } from '@/system/models';
|
import { Tenant } from '@/system/models';
|
||||||
|
import { parseBoolean, transformToMap } from 'utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class JournalSheetService {
|
export class JournalSheetService {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
private tenancy: TenancyService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
inventoryService: InventoryService;
|
private inventoryService: InventoryService;
|
||||||
|
|
||||||
@Inject('logger')
|
|
||||||
logger: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default journal sheet filter queyr.
|
* Default journal sheet filter queyr.
|
||||||
@@ -67,9 +68,13 @@ export default class JournalSheetService {
|
|||||||
/**
|
/**
|
||||||
* Journal sheet.
|
* Journal sheet.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IJournalSheetFilterQuery} query
|
* @param {IJournalReportQuery} query
|
||||||
|
* @returns {Promise<IJournalSheet>}
|
||||||
*/
|
*/
|
||||||
async journalSheet(tenantId: number, query: IJournalReportQuery) {
|
async journalSheet(
|
||||||
|
tenantId: number,
|
||||||
|
query: IJournalReportQuery
|
||||||
|
): Promise<IJournalSheet> {
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
const { accountRepository, transactionsRepository, contactRepository } =
|
const { accountRepository, transactionsRepository, contactRepository } =
|
||||||
this.tenancy.repositories(tenantId);
|
this.tenancy.repositories(tenantId);
|
||||||
@@ -80,11 +85,6 @@ export default class JournalSheetService {
|
|||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
this.logger.info('[journal] trying to calculate the report.', {
|
|
||||||
tenantId,
|
|
||||||
filter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tenant = await Tenant.query()
|
const tenant = await Tenant.query()
|
||||||
.findById(tenantId)
|
.findById(tenantId)
|
||||||
.withGraphFetched('metadata');
|
.withGraphFetched('metadata');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<IJournalTable>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
tenantId: number,
|
||||||
|
query: IJournalReportQuery
|
||||||
|
): Promise<IJournalTable> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
export enum ROW_TYPE {
|
||||||
|
ENTRY = 'ENTRY',
|
||||||
|
TOTAL = 'TOTAL'
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendInvoiceMailReminder {
|
export class SendInvoiceMailReminder {
|
||||||
@@ -66,10 +67,10 @@ export class SendInvoiceMailReminder {
|
|||||||
) {
|
) {
|
||||||
const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId);
|
const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId);
|
||||||
|
|
||||||
const messageOpts = {
|
const messageOpts = parseAndValidateMailOptions(
|
||||||
...localMessageOpts,
|
localMessageOpts,
|
||||||
...messageOptions,
|
messageOptions
|
||||||
};
|
);
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(messageOpts.subject)
|
.setSubject(messageOpts.subject)
|
||||||
.setTo(messageOpts.to)
|
.setTo(messageOpts.to)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Container, { Service } from 'typedi';
|
import Container, { Service } from 'typedi';
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification';
|
import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -22,8 +21,6 @@ export class PaymentReceiveMailNotificationJob {
|
|||||||
const { tenantId, paymentReceiveId, messageDTO } = job.attrs.data;
|
const { tenantId, paymentReceiveId, messageDTO } = job.attrs.data;
|
||||||
const paymentMail = Container.get(SendPaymentReceiveMailNotification);
|
const paymentMail = Container.get(SendPaymentReceiveMailNotification);
|
||||||
|
|
||||||
console.log(tenantId, paymentReceiveId, messageDTO);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await paymentMail.sendMail(tenantId, paymentReceiveId, messageDTO);
|
await paymentMail.sendMail(tenantId, paymentReceiveId, messageDTO);
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export class SaleReceiptApplication {
|
|||||||
* Sends the receipt mail of the given sale receipt.
|
* Sends the receipt mail of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleReceiptId
|
* @param {number} saleReceiptId
|
||||||
|
* @param {SaleReceiptMailOptsDTO} messageOpts
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public sendSaleReceiptMail(
|
public sendSaleReceiptMail(
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ export default function Dashboard() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
<DashboardUniversalSearch />
|
<DashboardUniversalSearch />
|
||||||
<DialogsContainer />
|
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
|
<DialogsContainer />
|
||||||
<DrawersContainer />
|
<DrawersContainer />
|
||||||
<AlertsContainer />
|
<AlertsContainer />
|
||||||
</DashboardProvider>
|
</DashboardProvider>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function AccountsDataTable({
|
|||||||
// #withAlertsDialog
|
// #withAlertsDialog
|
||||||
openAlert,
|
openAlert,
|
||||||
|
|
||||||
// #withDial
|
// #withDialog
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|
||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
|||||||
import authenticationRoutes from '@/routes/authentication';
|
import authenticationRoutes from '@/routes/authentication';
|
||||||
import { Icon, FormattedMessage as T } from '@/components';
|
import { Icon, FormattedMessage as T } from '@/components';
|
||||||
import { useIsAuthenticated } from '@/hooks/state';
|
import { useIsAuthenticated } from '@/hooks/state';
|
||||||
|
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
||||||
|
|
||||||
import '@/style/pages/Authentication/Auth.scss';
|
import '@/style/pages/Authentication/Auth.scss';
|
||||||
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
|
||||||
|
|
||||||
export function Authentication() {
|
export function Authentication() {
|
||||||
const to = { pathname: '/' };
|
const to = { pathname: '/' };
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
|
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
|
||||||
|
import { GeneralLedgerSheetExportMenu } from './components';
|
||||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
@@ -84,11 +85,18 @@ function GeneralLedgerActionsBar({
|
|||||||
icon={<Icon icon="print-16" iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Popover
|
||||||
className={Classes.MINIMAL}
|
content={<GeneralLedgerSheetExportMenu />}
|
||||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
text={<T id={'export'} />}
|
placement="bottom-start"
|
||||||
/>
|
minimal
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
|
text={<T id={'export'} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const GeneralLedgerHeaderDimensionsPanelContext = React.createContext();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* General Ledger Header Dimensions Panel provider.
|
* General Ledger Header Dimensions Panel provider.
|
||||||
* @returns
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
function GeneralLedgerHeaderDimensionsPanelProvider({ query, ...props }) {
|
function GeneralLedgerHeaderDimensionsPanelProvider({ query, ...props }) {
|
||||||
// Features guard.
|
// Features guard.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function GeneralLedgerProvider({ query, ...props }) {
|
|||||||
sheetRefresh: refetch,
|
sheetRefresh: refetch,
|
||||||
isFetching,
|
isFetching,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
httpRequest: requestQuery
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FinancialReportPage name={'general-ledger-sheet'}>
|
<FinancialReportPage name={'general-ledger-sheet'}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
|
||||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||||
import { useGeneralLedgerTableColumns } from './components';
|
import { useGeneralLedgerTableColumns } from './dynamicColumns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger table.
|
* General ledger table.
|
||||||
@@ -21,7 +21,7 @@ import { useGeneralLedgerTableColumns } from './components';
|
|||||||
export default function GeneralLedgerTable({ companyName }) {
|
export default function GeneralLedgerTable({ companyName }) {
|
||||||
// General ledger context.
|
// General ledger context.
|
||||||
const {
|
const {
|
||||||
generalLedger: { tableRows, query },
|
generalLedger: { query, table },
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useGeneralLedgerContext();
|
} = useGeneralLedgerContext();
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ export default function GeneralLedgerTable({ companyName }) {
|
|||||||
|
|
||||||
// Default expanded rows of general ledger table.
|
// Default expanded rows of general ledger table.
|
||||||
const expandedRows = useMemo(
|
const expandedRows = useMemo(
|
||||||
() => defaultExpanderReducer(tableRows, 1),
|
() => defaultExpanderReducer(table.rows, 1),
|
||||||
[tableRows],
|
[table.rows],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,7 +48,7 @@ export default function GeneralLedgerTable({ companyName }) {
|
|||||||
'this_report_does_not_contain_any_data_between_date_period',
|
'this_report_does_not_contain_any_data_between_date_period',
|
||||||
)}
|
)}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={tableRows}
|
data={table.rows}
|
||||||
rowClassNames={tableRowTypesToClassnames}
|
rowClassNames={tableRowTypesToClassnames}
|
||||||
expanded={expandedRows}
|
expanded={expandedRows}
|
||||||
virtualizedRows={true}
|
virtualizedRows={true}
|
||||||
@@ -79,23 +79,20 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr:not(.no-results) .td:not(:first-of-type) {
|
.tr:not(.no-results) .td:not(:first-of-type) {
|
||||||
border-left: 1px solid #ececec;
|
border-left: 1px solid #ececec;
|
||||||
}
|
}
|
||||||
.tr:last-child .td {
|
.tr:last-child .td {
|
||||||
border-bottom: 1px solid #ececec;
|
border-bottom: 1px solid #ececec;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr.row_type {
|
.tr.row_type {
|
||||||
&--ACCOUNT_ROW {
|
&--ACCOUNT {
|
||||||
.td {
|
.td {
|
||||||
&.date {
|
&.date {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
.cell-inner {
|
.cell-inner {
|
||||||
white-space: nowrap;
|
position: absolute;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +100,6 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
|||||||
border-top: 1px solid #ddd;
|
border-top: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--OPENING_BALANCE,
|
&--OPENING_BALANCE,
|
||||||
&--CLOSING_BALANCE {
|
&--CLOSING_BALANCE {
|
||||||
.amount {
|
.amount {
|
||||||
|
|||||||
@@ -1,107 +1,31 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import classNames from 'classnames';
|
||||||
import { Button } from '@blueprintjs/core';
|
import {
|
||||||
import { FormattedMessage as T, Icon, If } from '@/components';
|
Button,
|
||||||
|
Classes,
|
||||||
|
Intent,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
ProgressBar,
|
||||||
|
Text,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
Icon,
|
||||||
|
If,
|
||||||
|
Stack,
|
||||||
|
AppToaster,
|
||||||
|
} from '@/components';
|
||||||
|
|
||||||
import { getColumnWidth } from '@/utils';
|
|
||||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||||
|
|
||||||
import { FinancialComputeAlert } from '../FinancialReportPage';
|
import { FinancialComputeAlert } from '../FinancialReportPage';
|
||||||
import { Align } from '@/constants';
|
import {
|
||||||
|
useGeneralLedgerSheetCsvExport,
|
||||||
/**
|
useGeneralLedgerSheetXlsxExport,
|
||||||
* Retrieve the general ledger table columns.
|
} from '@/hooks/query';
|
||||||
*/
|
|
||||||
export function useGeneralLedgerTableColumns() {
|
|
||||||
// General ledger context.
|
|
||||||
const {
|
|
||||||
generalLedger: { tableRows },
|
|
||||||
} = useGeneralLedgerContext();
|
|
||||||
|
|
||||||
return React.useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Header: intl.get('date'),
|
|
||||||
accessor: 'date',
|
|
||||||
className: 'date',
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('account_name'),
|
|
||||||
accessor: 'name',
|
|
||||||
className: 'name',
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('transaction_type'),
|
|
||||||
accessor: 'reference_type_formatted',
|
|
||||||
className: 'transaction_type',
|
|
||||||
width: 125,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('transaction_number'),
|
|
||||||
accessor: 'reference_id',
|
|
||||||
className: 'transaction_number',
|
|
||||||
width: 100,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('description'),
|
|
||||||
accessor: 'note',
|
|
||||||
className: 'description',
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('credit'),
|
|
||||||
accessor: 'formatted_credit',
|
|
||||||
className: 'credit',
|
|
||||||
width: getColumnWidth(tableRows, 'formatted_credit', {
|
|
||||||
minWidth: 100,
|
|
||||||
magicSpacing: 10,
|
|
||||||
}),
|
|
||||||
textOverview: true,
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('debit'),
|
|
||||||
accessor: 'formatted_debit',
|
|
||||||
className: 'debit',
|
|
||||||
width: getColumnWidth(tableRows, 'formatted_debit', {
|
|
||||||
minWidth: 100,
|
|
||||||
magicSpacing: 10,
|
|
||||||
}),
|
|
||||||
textOverview: true,
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('amount'),
|
|
||||||
accessor: 'formatted_amount',
|
|
||||||
className: 'amount',
|
|
||||||
width: getColumnWidth(tableRows, 'formatted_amount', {
|
|
||||||
minWidth: 100,
|
|
||||||
magicSpacing: 10,
|
|
||||||
}),
|
|
||||||
textOverview: true,
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('running_balance'),
|
|
||||||
accessor: 'formatted_running_balance',
|
|
||||||
className: 'running_balance',
|
|
||||||
width: getColumnWidth(tableRows, 'formatted_running_balance', {
|
|
||||||
minWidth: 100,
|
|
||||||
magicSpacing: 10,
|
|
||||||
}),
|
|
||||||
textOverview: true,
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[tableRows],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet alerts.
|
* General ledger sheet alerts.
|
||||||
@@ -144,3 +68,93 @@ export function GeneralLedgerSheetLoadingBar() {
|
|||||||
</If>
|
</If>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the G/L sheet export menu.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export const GeneralLedgerSheetExportMenu = () => {
|
||||||
|
const toastKey = useRef(null);
|
||||||
|
const commonToastConfig = {
|
||||||
|
isCloseButtonShown: true,
|
||||||
|
timeout: 2000,
|
||||||
|
};
|
||||||
|
const { httpRequest } = useGeneralLedgerContext();
|
||||||
|
|
||||||
|
const openProgressToast = (amount: number) => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={8}>
|
||||||
|
<Text>The report has been exported successfully.</Text>
|
||||||
|
<ProgressBar
|
||||||
|
className={classNames('toast-progress', {
|
||||||
|
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
|
||||||
|
})}
|
||||||
|
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
|
||||||
|
value={amount / 100}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Export the report to xlsx.
|
||||||
|
const { mutateAsync: xlsxExport } = useGeneralLedgerSheetXlsxExport(
|
||||||
|
httpRequest,
|
||||||
|
{
|
||||||
|
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||||
|
if (!toastKey.current) {
|
||||||
|
toastKey.current = AppToaster.show({
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AppToaster.show(
|
||||||
|
{
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
},
|
||||||
|
toastKey.current,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Export the report to csv.
|
||||||
|
const { mutateAsync: csvExport } = useGeneralLedgerSheetCsvExport(
|
||||||
|
httpRequest,
|
||||||
|
{
|
||||||
|
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||||
|
if (!toastKey.current) {
|
||||||
|
toastKey.current = AppToaster.show({
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AppToaster.show(
|
||||||
|
{
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
},
|
||||||
|
toastKey.current,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Handle csv export button click.
|
||||||
|
const handleCsvExportBtnClick = () => {
|
||||||
|
csvExport();
|
||||||
|
};
|
||||||
|
// Handle xlsx export button click.
|
||||||
|
const handleXlsxExportBtnClick = () => {
|
||||||
|
xlsxExport();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
text={'XLSX (Microsoft Excel)'}
|
||||||
|
onClick={handleXlsxExportBtnClick}
|
||||||
|
/>
|
||||||
|
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { getColumnWidth } from '@/utils';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||||
|
import { Align } from '@/constants';
|
||||||
|
|
||||||
|
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||||
|
|
||||||
|
const getReportColWidth = (data, accessor, headerText) => {
|
||||||
|
return getColumnWidth(
|
||||||
|
data,
|
||||||
|
accessor,
|
||||||
|
{ magicSpacing: 10, minWidth: 100 },
|
||||||
|
headerText,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account name column mapper.
|
||||||
|
*/
|
||||||
|
const commonColumnMapper = R.curry((data, column) => {
|
||||||
|
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: column.key,
|
||||||
|
Header: column.label,
|
||||||
|
accessor,
|
||||||
|
className: column.key,
|
||||||
|
textOverview: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric columns accessor.
|
||||||
|
*/
|
||||||
|
const numericColumnAccessor = R.curry((data, column) => {
|
||||||
|
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||||
|
const width = getReportColWidth(data, accessor, column.label);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
align: Align.Right,
|
||||||
|
width,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date column accessor.
|
||||||
|
*/
|
||||||
|
const dateColumnAccessor = R.curry((column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 120,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction type column accessor.
|
||||||
|
*/
|
||||||
|
const transactionTypeColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 125,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction number column accessor.
|
||||||
|
*/
|
||||||
|
const transactionIdColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 80,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const dynamiColumnMapper = R.curry((data, column) => {
|
||||||
|
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
|
||||||
|
R.when(
|
||||||
|
R.pathEq(['key'], 'reference_type'),
|
||||||
|
transactionTypeColumnAccessor,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
R.pathEq(['key'], 'reference_number'),
|
||||||
|
transactionIdColumnAccessor,
|
||||||
|
),
|
||||||
|
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||||
|
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||||
|
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
|
||||||
|
R.when(R.pathEq(['key'], 'running_balance'), _numericColumnAccessor),
|
||||||
|
commonColumnMapper(data),
|
||||||
|
)(column);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes the dynamic columns that fetched from request to columns to table component.
|
||||||
|
*/
|
||||||
|
export const dynamicColumns = R.curry((data, columns) => {
|
||||||
|
return R.map(dynamiColumnMapper(data), columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the G/L sheet table columns for table component.
|
||||||
|
*/
|
||||||
|
export const useGeneralLedgerTableColumns = () => {
|
||||||
|
const { generalLedger } = useGeneralLedgerContext();
|
||||||
|
|
||||||
|
if (!generalLedger) {
|
||||||
|
throw new Error('asdfadsf');
|
||||||
|
}
|
||||||
|
const { table } = generalLedger;
|
||||||
|
|
||||||
|
return dynamicColumns(table.rows, table.columns);
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@ import withJournal from './withJournal';
|
|||||||
|
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import { useJournalSheetContext } from './JournalProvider';
|
import { useJournalSheetContext } from './JournalProvider';
|
||||||
|
import { JournalSheetExportMenu } from './components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal sheeet - Actions bar.
|
* Journal sheeet - Actions bar.
|
||||||
@@ -85,11 +86,18 @@ function JournalActionsBar({
|
|||||||
icon={<Icon icon="print-16" iconSize={16} />}
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
text={<T id={'print'} />}
|
text={<T id={'print'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Popover
|
||||||
className={Classes.MINIMAL}
|
content={<JournalSheetExportMenu />}
|
||||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
text={<T id={'export'} />}
|
placement="bottom-start"
|
||||||
/>
|
minimal
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
|
text={<T id={'export'} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ function JournalSheetProvider({ query, ...props }) {
|
|||||||
isLoading,
|
isLoading,
|
||||||
isFetching,
|
isFetching,
|
||||||
refetchSheet: refetch,
|
refetchSheet: refetch,
|
||||||
|
httpQuery: requestQuery
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
TableVirtualizedListRows,
|
TableVirtualizedListRows,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
|
||||||
import { useJournalTableColumns } from './components';
|
|
||||||
import { useJournalSheetContext } from './JournalProvider';
|
import { useJournalSheetContext } from './JournalProvider';
|
||||||
|
|
||||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
|
import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
|
||||||
|
import { useJournalSheetColumns } from './dynamicColumns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal sheet table.
|
* Journal sheet table.
|
||||||
@@ -23,12 +23,12 @@ import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
|
|||||||
export function JournalTable({ companyName }) {
|
export function JournalTable({ companyName }) {
|
||||||
// Journal sheet context.
|
// Journal sheet context.
|
||||||
const {
|
const {
|
||||||
journalSheet: { tableRows, query },
|
journalSheet: { table, query },
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useJournalSheetContext();
|
} = useJournalSheetContext();
|
||||||
|
|
||||||
// Retreive the journal table columns.
|
// Retrieves the journal table columns.
|
||||||
const columns = useJournalTableColumns();
|
const columns = useJournalSheetColumns();
|
||||||
|
|
||||||
// Default expanded rows of general journal table.
|
// Default expanded rows of general journal table.
|
||||||
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
|
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
|
||||||
@@ -39,13 +39,13 @@ export function JournalTable({ companyName }) {
|
|||||||
sheetType={intl.get('journal_sheet')}
|
sheetType={intl.get('journal_sheet')}
|
||||||
fromDate={query.from_date}
|
fromDate={query.from_date}
|
||||||
toDate={query.to_date}
|
toDate={query.to_date}
|
||||||
name="journal"
|
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
name="journal"
|
||||||
>
|
>
|
||||||
<JournalDataTable
|
<JournalDataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={tableRows}
|
data={table.rows}
|
||||||
rowClassNames={tableRowTypesToClassnames}
|
rowClassNames={tableRowTypesToClassnames}
|
||||||
noResults={intl.get(
|
noResults={intl.get(
|
||||||
'this_report_does_not_contain_any_data_between_date_period',
|
'this_report_does_not_contain_any_data_between_date_period',
|
||||||
@@ -83,10 +83,9 @@ const JournalDataTable = styled(ReportDataTable)`
|
|||||||
border-bottom: 1px solid #dbdbdb;
|
border-bottom: 1px solid #dbdbdb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tr.row_type--TOTAL_ENTRIES {
|
.tr.row_type--TOTAL{
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr:not(.no-results) {
|
.tr:not(.no-results) {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,31 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
import {
|
||||||
import { Button } from '@blueprintjs/core';
|
Button,
|
||||||
|
Classes,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
ProgressBar,
|
||||||
|
Text,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import { Icon, If, FormattedMessage as T } from '@/components';
|
import {
|
||||||
|
AppToaster,
|
||||||
|
Icon,
|
||||||
|
If,
|
||||||
|
Stack,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from '@/components';
|
||||||
import { useJournalSheetContext } from './JournalProvider';
|
import { useJournalSheetContext } from './JournalProvider';
|
||||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||||
import { FinancialComputeAlert } from '../FinancialReportPage';
|
import { FinancialComputeAlert } from '../FinancialReportPage';
|
||||||
|
|
||||||
import { Align } from '@/constants';
|
import {
|
||||||
|
useJournalSheetCsvExport,
|
||||||
/**
|
useJournalSheetXlsxExport,
|
||||||
* Retrieve the journal table columns.
|
} from '@/hooks/query';
|
||||||
*/
|
|
||||||
export const useJournalTableColumns = () => {
|
|
||||||
return React.useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Header: intl.get('date'),
|
|
||||||
accessor: (row) =>
|
|
||||||
row.date ? moment(row.date).format('YYYY MMM DD') : '',
|
|
||||||
className: 'date',
|
|
||||||
width: 100,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('transaction_type'),
|
|
||||||
accessor: 'reference_type_formatted',
|
|
||||||
className: 'reference_type_formatted',
|
|
||||||
width: 120,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('num'),
|
|
||||||
accessor: 'transaction_number',
|
|
||||||
className: 'reference_id',
|
|
||||||
width: 70,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('description'),
|
|
||||||
accessor: 'note',
|
|
||||||
className: 'note',
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('acc_code'),
|
|
||||||
accessor: 'account_code',
|
|
||||||
width: 95,
|
|
||||||
className: 'account_code',
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('account'),
|
|
||||||
accessor: 'account_name',
|
|
||||||
className: 'account_name',
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('credit'),
|
|
||||||
accessor: 'formatted_credit',
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: intl.get('debit'),
|
|
||||||
accessor: 'formatted_debit',
|
|
||||||
align: Align.Right,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal sheet loading bar.
|
* Journal sheet loading bar.
|
||||||
@@ -115,3 +69,87 @@ export function JournalSheetAlerts() {
|
|||||||
</FinancialComputeAlert>
|
</FinancialComputeAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the journal sheet export menu.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export const JournalSheetExportMenu = () => {
|
||||||
|
const toastKey = useRef(null);
|
||||||
|
const commonToastConfig = {
|
||||||
|
isCloseButtonShown: true,
|
||||||
|
timeout: 2000,
|
||||||
|
};
|
||||||
|
const { httpQuery } = useJournalSheetContext();
|
||||||
|
|
||||||
|
const openProgressToast = (amount: number) => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={8}>
|
||||||
|
<Text>The report has been exported successfully.</Text>
|
||||||
|
<ProgressBar
|
||||||
|
className={classNames('toast-progress', {
|
||||||
|
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
|
||||||
|
})}
|
||||||
|
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
|
||||||
|
value={amount / 100}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Exports the report to xlsx.
|
||||||
|
const { mutateAsync: xlsxExport } = useJournalSheetXlsxExport(httpQuery, {
|
||||||
|
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||||
|
if (!toastKey.current) {
|
||||||
|
toastKey.current = AppToaster.show({
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AppToaster.show(
|
||||||
|
{
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
},
|
||||||
|
toastKey.current,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Exports the report to csv.
|
||||||
|
const { mutateAsync: csvExport } = useJournalSheetCsvExport(httpQuery, {
|
||||||
|
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||||
|
if (!toastKey.current) {
|
||||||
|
toastKey.current = AppToaster.show({
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AppToaster.show(
|
||||||
|
{
|
||||||
|
message: openProgressToast(xlsxExportProgress),
|
||||||
|
...commonToastConfig,
|
||||||
|
},
|
||||||
|
toastKey.current,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Handle csv export button click.
|
||||||
|
const handleCsvExportBtnClick = () => {
|
||||||
|
csvExport();
|
||||||
|
};
|
||||||
|
// Handle xlsx export button click.
|
||||||
|
const handleXlsxExportBtnClick = () => {
|
||||||
|
xlsxExport();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
text={'XLSX (Microsoft Excel)'}
|
||||||
|
onClick={handleXlsxExportBtnClick}
|
||||||
|
/>
|
||||||
|
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Align } from '@/constants';
|
||||||
|
import { getColumnWidth } from '@/utils';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { useJournalSheetContext } from './JournalProvider';
|
||||||
|
|
||||||
|
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||||
|
|
||||||
|
const getReportColWidth = (data, accessor, headerText) => {
|
||||||
|
return getColumnWidth(
|
||||||
|
data,
|
||||||
|
accessor,
|
||||||
|
{ magicSpacing: 10, minWidth: 100 },
|
||||||
|
headerText,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common column mapper.
|
||||||
|
*/
|
||||||
|
const commonAccessor = R.curry((data, column) => {
|
||||||
|
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: column.key,
|
||||||
|
Header: column.label,
|
||||||
|
accessor,
|
||||||
|
className: column.key,
|
||||||
|
textOverview: true,
|
||||||
|
align: Align.Left,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric columns accessor.
|
||||||
|
*/
|
||||||
|
const numericColumnAccessor = R.curry((data, column) => {
|
||||||
|
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||||
|
const width = getReportColWidth(data, accessor, column.label);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
align: Align.Right,
|
||||||
|
width,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date column accessor.
|
||||||
|
*/
|
||||||
|
const dateColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 100,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction type column accessor.
|
||||||
|
*/
|
||||||
|
const transactionTypeColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 120,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction number column accessor.
|
||||||
|
*/
|
||||||
|
const transactionNumberColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 70,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account code column accessor.
|
||||||
|
*/
|
||||||
|
const accountCodeColumnAccessor = (column) => {
|
||||||
|
return {
|
||||||
|
...column,
|
||||||
|
width: 70,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic column mapper.
|
||||||
|
* @param {} data -
|
||||||
|
* @param {} column -
|
||||||
|
*/
|
||||||
|
const dynamicColumnMapper = R.curry((data, column) => {
|
||||||
|
const _commonAccessor = commonAccessor(data);
|
||||||
|
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
|
||||||
|
R.when(
|
||||||
|
R.pathEq(['key'], 'transaction_type'),
|
||||||
|
transactionTypeColumnAccessor,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
R.pathEq(['key'], 'transaction_number'),
|
||||||
|
transactionNumberColumnAccessor,
|
||||||
|
),
|
||||||
|
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
|
||||||
|
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||||
|
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||||
|
_commonAccessor,
|
||||||
|
)(column);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes the fetched dynamic columns from the server to the columns to pass it
|
||||||
|
* to the table component.
|
||||||
|
*/
|
||||||
|
export const dynamicColumns = (columns, data) => {
|
||||||
|
return R.map(dynamicColumnMapper(data), columns);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the table columns of journal sheet.
|
||||||
|
*/
|
||||||
|
export const useJournalSheetColumns = () => {
|
||||||
|
const { journalSheet } = useJournalSheetContext();
|
||||||
|
|
||||||
|
if (!journalSheet) {
|
||||||
|
throw new Error('The journal sheet is not loaded');
|
||||||
|
}
|
||||||
|
const { table } = journalSheet;
|
||||||
|
|
||||||
|
return dynamicColumns(table.columns, table.rows);
|
||||||
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { castArray } from 'lodash';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { useAppQueryString } from '@/hooks';
|
import { useAppQueryString } from '@/hooks';
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useRequestQuery } from '../useQueryRequest';
|
import { useRequestQuery } from '../useQueryRequest';
|
||||||
import {
|
import {
|
||||||
generalLedgerTableRowsReducer,
|
|
||||||
journalTableRowsReducer,
|
|
||||||
inventoryValuationReducer,
|
inventoryValuationReducer,
|
||||||
purchasesByItemsReducer,
|
purchasesByItemsReducer,
|
||||||
salesByItemsReducer,
|
salesByItemsReducer,
|
||||||
@@ -167,21 +165,44 @@ export function useGeneralLedgerSheet(query, props) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
url: '/financial_statements/general_ledger',
|
url: '/financial_statements/general_ledger',
|
||||||
params: query,
|
params: query,
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json+table',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: (res) => ({
|
select: (res) => res.data,
|
||||||
tableRows: generalLedgerTableRowsReducer(res.data.data),
|
|
||||||
...res.data,
|
|
||||||
}),
|
|
||||||
defaultData: {
|
|
||||||
tableRows: [],
|
|
||||||
data: {},
|
|
||||||
query: {},
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export const useGeneralLedgerSheetXlsxExport = (query, args) => {
|
||||||
|
return useDownloadFile({
|
||||||
|
url: '/financial_statements/general_ledger',
|
||||||
|
config: {
|
||||||
|
headers: {
|
||||||
|
accept: 'application/xlsx',
|
||||||
|
},
|
||||||
|
params: query,
|
||||||
|
},
|
||||||
|
filename: 'general_ledger.xlsx',
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGeneralLedgerSheetCsvExport = (query, args) => {
|
||||||
|
return useDownloadFile({
|
||||||
|
url: '/financial_statements/general_ledger',
|
||||||
|
config: {
|
||||||
|
headers: {
|
||||||
|
accept: 'application/csv',
|
||||||
|
},
|
||||||
|
params: query,
|
||||||
|
},
|
||||||
|
filename: 'general_ledger.csv',
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve journal sheet.
|
* Retrieve journal sheet.
|
||||||
@@ -189,22 +210,49 @@ export function useGeneralLedgerSheet(query, props) {
|
|||||||
export function useJournalSheet(query, props) {
|
export function useJournalSheet(query, props) {
|
||||||
return useRequestQuery(
|
return useRequestQuery(
|
||||||
[t.FINANCIAL_REPORT, t.JOURNAL, query],
|
[t.FINANCIAL_REPORT, t.JOURNAL, query],
|
||||||
{ method: 'get', url: '/financial_statements/journal', params: query },
|
|
||||||
{
|
{
|
||||||
select: (res) => ({
|
method: 'get',
|
||||||
tableRows: journalTableRowsReducer(res.data.data),
|
url: '/financial_statements/journal',
|
||||||
...res.data,
|
params: query,
|
||||||
}),
|
headers: {
|
||||||
defaultData: {
|
Accept: 'application/json+table',
|
||||||
data: {},
|
|
||||||
tableRows: [],
|
|
||||||
query: {},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (res) => res.data,
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useJournalSheetXlsxExport = (query, args) => {
|
||||||
|
return useDownloadFile({
|
||||||
|
url: '/financial_statements/journal',
|
||||||
|
config: {
|
||||||
|
headers: {
|
||||||
|
accept: 'application/xlsx',
|
||||||
|
},
|
||||||
|
params: query,
|
||||||
|
},
|
||||||
|
filename: 'journal.xlsx',
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useJournalSheetCsvExport = (query, args) => {
|
||||||
|
return useDownloadFile({
|
||||||
|
url: '/financial_statements/journal',
|
||||||
|
config: {
|
||||||
|
headers: {
|
||||||
|
accept: 'application/csv',
|
||||||
|
},
|
||||||
|
params: query,
|
||||||
|
},
|
||||||
|
filename: 'journal.csv',
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve A/R aging summary report.
|
* Retrieve A/R aging summary report.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user